Skip to content

Comments

CLI live test framework + initial API group testing#3267

Open
Daniel Ayaz (danielayaz) wants to merge 1 commit intomainfrom
cli-live-test-framework
Open

CLI live test framework + initial API group testing#3267
Daniel Ayaz (danielayaz) wants to merge 1 commit intomainfrom
cli-live-test-framework

Conversation

@danielayaz
Copy link
Member

@danielayaz Daniel Ayaz (danielayaz) commented Feb 20, 2026

Release Notes

Breaking Changes

  • PLACEHOLDER

New Features

  • PLACEHOLDER

Bug Fixes

  • PLACEHOLDER

Checklist

  • I have successfully built and used a custom CLI binary, without linter issues from this PR.
  • I have clearly specified in the What section below whether this PR applies to Confluent Cloud, Confluent Platform, or both.
  • I have verified this PR in Confluent Cloud pre-prod or production environment, if applicable.
  • I have verified this PR in Confluent Platform on-premises environment, if applicable.
  • I have attached manual CLI verification results or screenshots in the Test & Review section below.
  • I have added appropriate CLI integration or unit tests for any new or updated commands and functionality.
  • I confirm that this PR introduces no breaking changes or backward compatibility issues.
  • I have indicated the potential customer impact if something goes wrong in the Blast Radius section below.
  • I have put checkmarks below confirming that the feature associated with this PR is enabled in:
    • Confluent Cloud prod
    • Confluent Cloud stag
    • Confluent Platform
    • Check this box if the feature is enabled for certain organizations only

What

Adds a live integration test framework for the CLI targeting Confluent Cloud. This includes:

  • New test/live/ directory with Go test files that exercise real CLI commands against live Confluent Cloud (environments, clusters, topics, API keys, service accounts)
  • Build tag-based test grouping (core, kafka, essential, all)
  • Makefile targets: live-test, live-test-core, live-test-essential
  • Semaphore CI promotion pipeline (.semaphore/live-tests.yml) for on-demand live test runs
  • .gitignore update for test/live/bin/

No existing commands or functionality are modified. This is a test-only addition.

Blast Radius

None. This PR adds only test infrastructure and CI pipeline promotions. No production CLI commands or customer-facing behavior is changed. The live test pipeline is manually triggered only.

References

https://confluentinc.atlassian.net/browse/APIE-864

Test & Review

  • Built CLI binary via make build-for-live-test successfully
  • Ran make live-test-core and make live-test-essential against live Confluent Cloud
  • Tests cover: environment CRUD, Kafka cluster lifecycle, topic CRUD, API key management, service account operations
 ~/c/cli  cli-live-test-framework *3 ?33  make live-test-essential                          ✔  2h 56m 52s  1.25.7 
go build -ldflags="-s -w -X main.commit=00000000 -X main.date=1970-01-01T00:00:00Z -X main.disableUpdates=true" \
		-o test/live/bin/confluent ./cmd/confluent
=== RUN   TestLive
=== RUN   TestLive/TestApiKeyCRUDLive
=== PAUSE TestLive/TestApiKeyCRUDLive
=== RUN   TestLive/TestEnvironmentCRUDLive
=== PAUSE TestLive/TestEnvironmentCRUDLive
=== RUN   TestLive/TestKafkaClusterCRUDLive
=== PAUSE TestLive/TestKafkaClusterCRUDLive
=== RUN   TestLive/TestKafkaTopicCRUDLive
=== PAUSE TestLive/TestKafkaTopicCRUDLive
=== RUN   TestLive/TestServiceAccountCRUDLive
=== PAUSE TestLive/TestServiceAccountCRUDLive
=== CONT  TestLive/TestApiKeyCRUDLive
=== CONT  TestLive/TestKafkaTopicCRUDLive
=== CONT  TestLive/TestKafkaClusterCRUDLive
=== CONT  TestLive/TestServiceAccountCRUDLive
=== CONT  TestLive/TestEnvironmentCRUDLive
=== RUN   TestLive/TestApiKeyCRUDLive/Create_service_account_for_API_key
=== RUN   TestLive/TestKafkaTopicCRUDLive/Use_environment
=== RUN   TestLive/TestKafkaClusterCRUDLive/Create_environment_for_kafka_test
=== RUN   TestLive/TestServiceAccountCRUDLive/Create_service_account
=== RUN   TestLive/TestEnvironmentCRUDLive/Create_environment
    environment_live_test.go:79: Captured env_id = env-ykw91o
=== RUN   TestLive/TestEnvironmentCRUDLive/Describe_environment
=== NAME  TestLive/TestKafkaClusterCRUDLive/Create_environment_for_kafka_test
    kafka_cluster_live_test.go:50: Captured env_id = env-wkwy35
=== RUN   TestLive/TestKafkaClusterCRUDLive/Use_environment
=== RUN   TestLive/TestKafkaTopicCRUDLive/Use_kafka_cluster
=== RUN   TestLive/TestEnvironmentCRUDLive/List_environments
=== RUN   TestLive/TestKafkaClusterCRUDLive/Create_basic_kafka_cluster
=== NAME  TestLive/TestApiKeyCRUDLive/Create_service_account_for_API_key
    api_key_live_test.go:84: Captured sa_id = sa-0x2pd29
=== RUN   TestLive/TestApiKeyCRUDLive/Create_API_key
=== RUN   TestLive/TestKafkaTopicCRUDLive/Create_topic
=== NAME  TestLive/TestServiceAccountCRUDLive/Create_service_account
    service_account_live_test.go:79: Captured sa_id = sa-81p8vpm
=== RUN   TestLive/TestServiceAccountCRUDLive/Describe_service_account
=== RUN   TestLive/TestEnvironmentCRUDLive/Update_environment_name
=== RUN   TestLive/TestServiceAccountCRUDLive/List_service_accounts
=== RUN   TestLive/TestEnvironmentCRUDLive/Describe_updated_environment
=== NAME  TestLive/TestApiKeyCRUDLive/Create_API_key
    api_key_live_test.go:84: Captured api_key_id = DBNRTDJY4ZGVW27S
=== RUN   TestLive/TestApiKeyCRUDLive/List_API_keys
=== RUN   TestLive/TestServiceAccountCRUDLive/Update_service_account_description
=== RUN   TestLive/TestEnvironmentCRUDLive/Delete_environment
=== RUN   TestLive/TestServiceAccountCRUDLive/Describe_updated_service_account
=== NAME  TestLive/TestKafkaClusterCRUDLive/Create_basic_kafka_cluster
    kafka_cluster_live_test.go:50: Captured cluster_id = lkc-qm2xx6
=== RUN   TestLive/TestKafkaClusterCRUDLive/Wait_for_cluster_provisioned
=== RUN   TestLive/TestKafkaTopicCRUDLive/List_topics
=== RUN   TestLive/TestServiceAccountCRUDLive/Delete_service_account
=== RUN   TestLive/TestEnvironmentCRUDLive/Verify_deletion
=== NAME  TestLive/TestEnvironmentCRUDLive
    live_test.go:280: Cleanup: confluent environment delete env-ykw91o --force
=== RUN   TestLive/TestApiKeyCRUDLive/Update_API_key_description
=== RUN   TestLive/TestApiKeyCRUDLive/Describe_updated_API_key
=== NAME  TestLive/TestEnvironmentCRUDLive
    live_test.go:285: Cleanup: resource already deleted, skipping
=== RUN   TestLive/TestServiceAccountCRUDLive/Verify_deletion
=== NAME  TestLive/TestKafkaTopicCRUDLive/List_topics
    kafka_topic_live_test.go:83:
        	Error Trace:	/Users/danielayaz/confluentinc/cli/test/live/live_test.go:228
        	            				/Users/danielayaz/confluentinc/cli/test/live/live_test.go:197
        	            				/Users/danielayaz/confluentinc/cli/test/live/kafka_topic_live_test.go:83
        	Error:      	"                  Name                 | Internal | Replication Factor | Partition Count  \n---------------------------------------+----------+--------------------+------------------\n  pksqlc-033r92-processing-log         | false    |                  3 |               8  \n  pksqlc-035075-processing-log         | false    |                  3 |               8  \n  pksqlc-0388kp-processing-log         | false    |                  3 |               8  \n  pksqlc-038mnq-processing-log         | false    |                  3 |               8  \n  pksqlc-039xyp-processing-log         | false    |                  3 |               8  \n  pksqlc-0dowj9-processing-log         | false    |                  3 |               8  \n  pksqlc-1n2qvz-processing-log         | false    |                  3 |               8  \n  pksqlc-1n52jj-processing-log         | false    |                  3 |               8  \n  pksqlc-1n55k5-processing-log         | false    |                  3 |               8  \n  pksqlc-1n97kv-processing-log         | false    |                  3 |               8  \n  pksqlc-1nd7g6-processing-log         | false    |                  3 |               8  \n  pksqlc-1ndgw5-processing-log         | false    |                  3 |               8  \n  pksqlc-1nnpxz-processing-log         | false    |                  3 |               8  \n  pksqlc-1w89x5-processing-log         | false    |                  3 |               8  \n  pksqlc-1w8pd5-processing-log         | false    |                  3 |               8  \n  pksqlc-211nqy-processing-log         | false    |                  3 |               8  \n  pksqlc-213p3o-processing-log         | false    |                  3 |               8  \n  pksqlc-21rm2q-processing-log         | false    |                  3 |               8  \n  pksqlc-2jpv1q-processing-log         | false    |                  3 |               8  \n  pksqlc-2jvo2q-processing-log         | false    |                  3 |               8  \n  pksqlc-2jzv1y-processing-log         | false    |                  3 |               8  \n  pksqlc-373q9w-processing-log         | false    |                  3 |               8  \n  pksqlc-373r5w-processing-log         | false    |                  3 |               8  \n  pksqlc-377dym-processing-log         | false    |                  3 |               8  \n  pksqlc-37r5nj-processing-log         | false    |                  3 |               8  \n  pksqlc-37rm20-processing-log         | false    |                  3 |               8  \n  pksqlc-3wo5yw-processing-log         | false    |                  3 |               8  \n  pksqlc-3wy3z2-processing-log         | false    |                  3 |               8  \n  pksqlc-5558wg-processing-log         | false    |                  3 |               8  \n  pksqlc-5577rz-processing-log         | false    |                  3 |               8  \n  pksqlc-55m18z-processing-log         | false    |                  3 |               8  \n  pksqlc-55wn8q-processing-log         | false    |                  3 |               8  \n  pksqlc-5q0m28-processing-log         | false    |                  3 |               8  \n  pksqlc-6g88n3-processing-log         | false    |                  3 |               8  \n  pksqlc-6g8kkj-processing-log         | false    |                  3 |               8  \n  pksqlc-6ggr02-processing-log         | false    |                  3 |               8  \n  pksqlc-6w5m13-processing-log         | false    |                  3 |               8  \n  pksqlc-6w5r73-processing-log         | false    |                  3 |               8  \n  pksqlc-6wp983-processing-log         | false    |                  3 |               8  \n  pksqlc-7555kp-processing-log         | false    |                  3 |               8  \n  pksqlc-75onko-processing-log         | false    |                  3 |               8  \n  pksqlc-75qn22-processing-log         | false    |                  3 |               8  \n  pksqlc-75yj2p-processing-log         | false    |                  3 |               8  \n  pksqlc-7q3jno-processing-log         | false    |                  3 |               8  \n  pksqlc-7qrojp-processing-log         | false    |                  3 |               8  \n  pksqlc-7qv21o-processing-log         | false    |                  3 |               8  \n  pksqlc-7qvpjp-processing-log         | false    |                  3 |               8  \n  pksqlc-8g1ygq-processing-log         | false    |                  3 |               8  \n  pksqlc-8gqox5-processing-log         | false    |                  3 |               8  \n  pksqlc-8gv82r-processing-log         | false    |                  3 |               8  \n  pksqlc-8v5mr0-processing-log         | false    |                  3 |               8  \n  pksqlc-9p5o2v-processing-log         | false    |                  3 |               8  \n  pksqlc-9p71ky-processing-log         | false    |                  3 |               8  \n  pksqlc-9pw0wv-processing-log         | false    |                  3 |               8  \n  pksqlc-9z0g2y-processing-log         | false    |                  3 |               8  \n  pksqlc-9z69gv-processing-log         | false    |                  3 |               8  \n  pksqlc-9z8mpy-processing-log         | false    |                  3 |               8  \n  pksqlc-d1o6jz-processing-log         | false    |                  3 |               8  \n  pksqlc-d1v07z-processing-log         | false    |                  3 |               8  \n  pksqlc-dv25jd-processing-log         | false    |                  3 |               8  \n  pksqlc-dv2gjo-processing-log         | false    |                  3 |               8  \n  pksqlc-dv70d7-processing-log         | false    |                  3 |               8  \n  pksqlc-g9q3mr-processing-log         | false    |                  3 |               8  \n  pksqlc-gwz6r3-processing-log         | false    |                  3 |               8  \n  pksqlc-gwzk01-processing-log         | false    |                  3 |               8  \n  pksqlc-j956nq-processing-log         | false    |                  3 |               8  \n  pksqlc-j957kp-processing-log         | false    |                  3 |               8  \n  pksqlc-j98g7w-processing-log         | false    |                  3 |               8  \n  pksqlc-j9z16m-processing-log         | false    |                  3 |               8  \n  pksqlc-jvkjkw-processing-log         | false    |                  3 |               8  \n  pksqlc-jvknx2-processing-log         | false    |                  3 |               8  \n  pksqlc-kjkr5v-processing-log         | false    |                  3 |               8  \n  pksqlc-kjqk9g-processing-log         | false    |                  3 |               8  \n  pksqlc-kjx2mp-processing-log         | false    |                  3 |               8  \n  pksqlc-m2rn71-processing-log         | false    |                  3 |               8  \n  pksqlc-mz72r7-processing-log         | false    |                  3 |               8  \n  pksqlc-mzk572-processing-log         | false    |                  3 |               8  \n  pksqlc-nk559k-processing-log         | false    |                  3 |               8  \n  pksqlc-nko38v-processing-log         | false    |                  3 |               8  \n  pksqlc-nkx3x3-processing-log         | false    |                  3 |               8  \n  pksqlc-nx7mrk-processing-log         | false    |                  3 |               8  \n  pksqlc-okx50p-processing-log         | false    |                  3 |               8  \n  pksqlc-oxoz0y-processing-log         | false    |                  3 |               8  \n  pksqlc-p97yj5-processing-log         | false    |                  3 |               8  \n  pksqlc-p9y3vk-processing-log         | false    |                  3 |               8  \n  pksqlc-p9yq5o-processing-log         | false    |                  3 |               8  \n  pksqlc-pj9r2y-processing-log         | false    |                  3 |               8  \n  pksqlc-pjkdoy-processing-log         | false    |                  3 |               8  \n  pksqlc-pjnzwm-processing-log         | false    |                  3 |               8  \n  pksqlc-pjrv52-processing-log         | false    |                  3 |               8  \n  pksqlc-qjm8x6-processing-log         | false    |                  3 |               8  \n  pksqlc-qjmkn6-processing-log         | false    |                  3 |               8  \n  pksqlc-qp8nw2-processing-log         | false    |                  3 |               8  \n  pksqlc-qp8r22-processing-log         | false    |                  3 |               8  \n  pksqlc-qpj7jd-processing-log         | false    |                  3 |               8  \n  pksqlc-qpp6md-processing-log         | false    |                  3 |               8  \n  pksqlc-qppv72-processing-log         | false    |                  3 |               8  \n  pksqlc-qpzxg6-processing-log         | false    |                  3 |               8  \n  pksqlc-r0pwd9-processing-log         | false    |                  3 |               8  \n  pksqlc-rj7nvp-processing-log         | false    |                  3 |               8  \n  pksqlc-rj7qop-processing-log         | false    |                  3 |               8  \n  pksqlc-rjrrn7-processing-log         | false    |                  3 |               8  \n  pksqlc-v6pxv0-processing-log         | false    |                  3 |               8  \n  pksqlc-v6r8np-processing-log         | false    |                  3 |               8  \n  pksqlc-vnx235-processing-log         | false    |                  3 |               8  \n  pksqlc-vnxqxn-processing-log         | false    |                  3 |               8  \n  pksqlc-w1903m-processing-log         | false    |                  3 |               8  \n  pksqlc-w19zm9-processing-log         | false    |                  3 |               8  \n  pksqlc-w1dg2g-processing-log         | false    |                  3 |               8  \n  pksqlc-x710oz-processing-log         | false    |                  3 |               8  \n  pksqlc-x71nmz-processing-log         | false    |                  3 |               8  \n  pksqlc-x7298z-processing-log         | false    |                  3 |               8  \n  pksqlc-x72w9z-processing-log         | false    |                  3 |               8  \n  pksqlc-xxokyq-processing-log         | false    |                  3 |               8  \n  pksqlc-xxq6xq-processing-log         | false    |                  3 |               8  \n  pksqlc-y627jk-processing-log         | false    |                  3 |               8  \n  pksqlc-y6mok7-processing-log         | false    |                  3 |               8  \n  pksqlc-y6qk9k-processing-log         | false    |                  3 |               8  \n  pksqlc-y6qq3p-processing-log         | false    |                  3 |               8  \n  pksqlc-yydr6j-processing-log         | false    |                  3 |               8  \n  pksqlc-yyr3g6-processing-log         | false    |                  3 |               8  \n  pksqlc-yyrxnj-processing-log         | false    |                  3 |               8  \n  pksqlc-z63gqd-processing-log         | false    |                  3 |               8  \n  pksqlc-z66660-processing-log         | false    |                  3 |               8  \n  pksqlc-z6g1yz-processing-log         | false    |                  3 |               8  \n  pksqlc-z6qqn0-processing-log         | false    |                  3 |               8  \n  pksqlc-z6qrvd-processing-log         | false    |                  3 |               8  \n  pksqlc-z6y0p3-processing-log         | false    |                  3 |               8  \n  pksqlc-zp0vm0-processing-log         | false    |                  3 |               8  \n  pksqlc-zp8dz3-processing-log         | false    |                  3 |               8  \n  tf-live-connector-update-13410-topic | false    |                  3 |               1  \n  tf-live-connector-update-13698-topic | false    |                  3 |               1  \n  tf-live-connector-update-20783-topic | false    |                  3 |               1  \n  tf-live-connector-update-25668-topic | false    |                  3 |               1  \n  tf-live-connector-update-27305-topic | false    |                  3 |               1  \n  tf-live-connector-update-34512-topic | false    |                  3 |               1  \n  tf-live-connector-update-41757-topic | false    |                  3 |               1  \n  tf-live-connector-update-44060-topic | false    |                  3 |               1  \n  tf-live-connector-update-61562-topic | false    |                  3 |               1  \n  tf-live-connector-update-62442-topic | false    |                  3 |               1  \n  tf-live-connector-update-66061-topic | false    |                  3 |               1  \n  tf-live-connector-update-69340-topic | false    |                  3 |               1  \n  tf-live-connector-update-69421-topic | false    |                  3 |               1  \n  tf-live-connector-update-76012-topic | false    |                  3 |               1  \n  tf-live-connector-update-80099-topic | false    |                  3 |               1  \n  tf-live-connector-update-81912-topic | false    |                  3 |               1  \n  tf-live-connector-update-82372-topic | false    |                  3 |               1  \n  tf-live-connector-update-8535-topic  | false    |                  3 |               1  \n  tf-live-connector-update-90384-topic | false    |                  3 |               1  \n  tf-live-connector-update-95600-topic | false    |                  3 |               1  \n  tf-live-connector-update-99365-topic | false    |                  3 |               1  \n" does not contain "cli-live-topic-211366"
        	Test:       	TestLive/TestKafkaTopicCRUDLive/List_topics
        	Messages:   	output of 'List topics' missing expected string "cli-live-topic-211366"
=== RUN   TestLive/TestKafkaTopicCRUDLive/Describe_topic
=== NAME  TestLive/TestServiceAccountCRUDLive
    live_test.go:280: Cleanup: confluent iam service-account delete sa-81p8vpm --force
=== RUN   TestLive/TestKafkaClusterCRUDLive/List_kafka_clusters
=== NAME  TestLive/TestServiceAccountCRUDLive
    live_test.go:285: Cleanup: resource already deleted, skipping
=== RUN   TestLive/TestKafkaClusterCRUDLive/Update_cluster_name
=== RUN   TestLive/TestApiKeyCRUDLive/Delete_API_key
=== RUN   TestLive/TestKafkaTopicCRUDLive/Update_topic_config
=== NAME  TestLive/TestApiKeyCRUDLive
    live_test.go:280: Cleanup: confluent api-key delete DBNRTDJY4ZGVW27S --force
    live_test.go:285: Cleanup: resource already deleted, skipping
    live_test.go:280: Cleanup: confluent iam service-account delete sa-0x2pd29 --force
=== RUN   TestLive/TestKafkaTopicCRUDLive/Describe_updated_topic_config
=== RUN   TestLive/TestKafkaClusterCRUDLive/Describe_updated_cluster
=== RUN   TestLive/TestKafkaTopicCRUDLive/Delete_topic
=== RUN   TestLive/TestKafkaClusterCRUDLive/Delete_cluster
=== NAME  TestLive/TestKafkaTopicCRUDLive
    live_test.go:280: Cleanup: confluent kafka topic delete cli-live-topic-211366 --cluster lkc-7g3pzj --environment env-zyg27z --force
=== NAME  TestLive/TestKafkaClusterCRUDLive
    live_test.go:280: Cleanup: confluent kafka cluster delete lkc-qm2xx6 --force --environment env-wkwy35
    live_test.go:285: Cleanup: resource already deleted, skipping
    live_test.go:280: Cleanup: confluent environment delete env-wkwy35 --force
=== NAME  TestLive/TestKafkaTopicCRUDLive
    live_test.go:285: Cleanup: resource already deleted, skipping
--- FAIL: TestLive (0.00s)
    --- PASS: TestLive/TestEnvironmentCRUDLive (7.44s)
        --- PASS: TestLive/TestEnvironmentCRUDLive/Create_environment (0.91s)
        --- PASS: TestLive/TestEnvironmentCRUDLive/Describe_environment (0.40s)
        --- PASS: TestLive/TestEnvironmentCRUDLive/List_environments (0.37s)
        --- PASS: TestLive/TestEnvironmentCRUDLive/Update_environment_name (0.40s)
        --- PASS: TestLive/TestEnvironmentCRUDLive/Describe_updated_environment (0.43s)
        --- PASS: TestLive/TestEnvironmentCRUDLive/Delete_environment (0.87s)
        --- PASS: TestLive/TestEnvironmentCRUDLive/Verify_deletion (0.37s)
    --- PASS: TestLive/TestServiceAccountCRUDLive (8.56s)
        --- PASS: TestLive/TestServiceAccountCRUDLive/Create_service_account (1.72s)
        --- PASS: TestLive/TestServiceAccountCRUDLive/Describe_service_account (0.39s)
        --- PASS: TestLive/TestServiceAccountCRUDLive/List_service_accounts (0.44s)
        --- PASS: TestLive/TestServiceAccountCRUDLive/Update_service_account_description (0.43s)
        --- PASS: TestLive/TestServiceAccountCRUDLive/Describe_updated_service_account (0.35s)
        --- PASS: TestLive/TestServiceAccountCRUDLive/Delete_service_account (1.23s)
        --- PASS: TestLive/TestServiceAccountCRUDLive/Verify_deletion (0.44s)
    --- PASS: TestLive/TestApiKeyCRUDLive (11.30s)
        --- PASS: TestLive/TestApiKeyCRUDLive/Create_service_account_for_API_key (1.79s)
        --- PASS: TestLive/TestApiKeyCRUDLive/Create_API_key (0.63s)
        --- PASS: TestLive/TestApiKeyCRUDLive/List_API_keys (1.57s)
        --- PASS: TestLive/TestApiKeyCRUDLive/Update_API_key_description (0.40s)
        --- PASS: TestLive/TestApiKeyCRUDLive/Describe_updated_API_key (1.47s)
        --- PASS: TestLive/TestApiKeyCRUDLive/Delete_API_key (0.65s)
    --- FAIL: TestLive/TestKafkaTopicCRUDLive (14.57s)
        --- PASS: TestLive/TestKafkaTopicCRUDLive/Use_environment (1.04s)
        --- PASS: TestLive/TestKafkaTopicCRUDLive/Use_kafka_cluster (0.69s)
        --- PASS: TestLive/TestKafkaTopicCRUDLive/Create_topic (1.62s)
        --- FAIL: TestLive/TestKafkaTopicCRUDLive/List_topics (1.35s)
        --- PASS: TestLive/TestKafkaTopicCRUDLive/Describe_topic (1.19s)
        --- PASS: TestLive/TestKafkaTopicCRUDLive/Update_topic_config (1.40s)
        --- PASS: TestLive/TestKafkaTopicCRUDLive/Describe_updated_topic_config (1.40s)
        --- PASS: TestLive/TestKafkaTopicCRUDLive/Delete_topic (1.49s)
    --- PASS: TestLive/TestKafkaClusterCRUDLive (15.62s)
        --- PASS: TestLive/TestKafkaClusterCRUDLive/Create_environment_for_kafka_test (1.03s)
        --- PASS: TestLive/TestKafkaClusterCRUDLive/Use_environment (0.44s)
        --- PASS: TestLive/TestKafkaClusterCRUDLive/Create_basic_kafka_cluster (1.70s)
        --- PASS: TestLive/TestKafkaClusterCRUDLive/Wait_for_cluster_provisioned (1.99s)
        --- PASS: TestLive/TestKafkaClusterCRUDLive/List_kafka_clusters (0.44s)
        --- PASS: TestLive/TestKafkaClusterCRUDLive/Update_cluster_name (2.19s)
        --- PASS: TestLive/TestKafkaClusterCRUDLive/Describe_updated_cluster (1.76s)
        --- PASS: TestLive/TestKafkaClusterCRUDLive/Delete_cluster (1.37s)
FAIL
FAIL	github.com/confluentinc/cli/v4/test/live	16.018s
FAIL
make[1]: *** [live-test] Error 1
make: *** [live-test-essential] Error 2

Copilot AI review requested due to automatic review settings February 20, 2026 20:44
@danielayaz Daniel Ayaz (danielayaz) requested a review from a team as a code owner February 20, 2026 20:44
@confluent-cla-assistant
Copy link

🎉 All Contributor License Agreements have been signed. Ready to merge.
Please push an empty commit if you would like to re-run the checks to verify CLA status for all contributors.


.PHONY: build-for-live-test
build-for-live-test:
go build -ldflags="-s -w -X main.commit=00000000 -X main.date=1970-01-01T00:00:00Z -X main.disableUpdates=true" \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

main.commit=00000000 -X main.date=1970-01-01T00:00:00Z -X main.disableUpdates=true

nit: do we need all of these flags?

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a comprehensive live integration test framework for the Confluent CLI that executes real CLI commands against live Confluent Cloud environments. The framework enables end-to-end testing of CLI operations including resource creation, updates, and deletion, with support for test grouping, concurrency, and automated cleanup.

Changes:

  • New test/live/ directory with Go-based live test framework supporting parallel test execution with isolated CLI config directories
  • Build tag-based test organization (core, kafka, essential, all) with Makefile targets for selective test execution
  • Semaphore CI pipeline (.semaphore/live-tests.yml) with manual promotion for on-demand live test runs with configurable cloud providers and regions

Reviewed changes

Copilot reviewed 11 out of 12 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
test/live/live_test.go Core test framework with suite setup, command execution, output validation, and state management
test/live/live_utils.go Utility functions for name generation, shell parsing, JSON extraction, and polling operations
test/live/environment_live_test.go CRUD tests for environment resources
test/live/service_account_live_test.go CRUD tests for service account resources
test/live/api_key_live_test.go CRUD tests for API key resources with service account dependencies
test/live/kafka_cluster_live_test.go CRUD tests for Kafka cluster resources including provisioning wait logic
test/live/kafka_topic_live_test.go CRUD tests for Kafka topic resources using pre-existing clusters
test/live/README.md Comprehensive documentation covering prerequisites, usage, test writing guide, and CI integration
Makefile New targets for building live test binary and running live tests with group filtering
.semaphore/semaphore.yml Added promotion for triggering live test pipeline with configurable parameters
.semaphore/live-tests.yml New CI pipeline for live tests with vault secret integration and Slack notifications
.gitignore Added exclusion for test/live/bin/ directory
Comments suppressed due to low confidence (4)

Makefile:148

  • The test timeout is set to 1440 minutes (24 hours) which seems excessive. While Kafka cluster provisioning can take ~5-10 minutes, a 24-hour timeout could cause CI jobs to hang for extremely long periods if a test gets stuck. The semaphore pipeline also has a 24-hour execution time limit (.semaphore/live-tests.yml line 19). Consider using a more reasonable timeout like 60-120 minutes which should be sufficient for the slowest operations (cluster provisioning + test execution) while preventing runaway tests.
			-tags="live_test,all" -timeout 1440m -parallel 10; \
	else \
		TAGS="live_test"; \
		for group in $$(echo "$(CLI_LIVE_TEST_GROUPS)" | tr ',' ' '); do \
			TAGS="$$TAGS,$$group"; \
		done; \
		CLI_LIVE_TEST=1 go test ./test/live/ -v -run=".*Live$$" \
			-tags="$$TAGS" -timeout 1440m -parallel 10; \

test/live/live_utils.go:93

  • The extractJSONField function silently returns an empty string when JSON parsing fails or the field doesn't exist. This can make debugging difficult because the caller won't know if the output wasn't JSON or if the field was missing. While this may be intentional for the current use case in waitForCondition where errors are tolerated during polling, it could lead to confusion when used elsewhere. Consider adding a logging statement when JSON unmarshaling fails, or provide a separate function that logs/returns errors for non-polling use cases.
// extractJSONField extracts a specific field value from JSON output.
func extractJSONField(t *testing.T, output, field string) string {
	t.Helper()
	var parsed map[string]interface{}
	if err := json.Unmarshal([]byte(strings.TrimSpace(output)), &parsed); err != nil {
		return ""
	}
	if val, ok := parsed[field]; ok {
		return fmt.Sprintf("%v", val)
	}
	return ""
}

.semaphore/live-tests.yml:29

  • The vault secrets are sourced but there's no explicit validation that the required environment variables (CONFLUENT_CLOUD_EMAIL, CONFLUENT_CLOUD_PASSWORD) are set before running the tests. If the vault secret is missing or misconfigured, the test will only fail when it tries to use these variables in setupTestContext. Consider adding explicit validation after sourcing the secrets to fail fast with a clear error message if required credentials are missing.
            - . vault-sem-get-secret v1/ci/kv/apif/cli/live-testing-data
            - . vault-sem-get-secret v1/ci/kv/apif/cli/slack-notifications-live-testing

test/live/live_test.go:262

  • The JSON validation logic uses fmt.Sprintf("%v", val) to convert field values to strings for comparison and emptiness checks. This can produce unexpected results for certain types. For example, a boolean false value will be converted to the string "false" which is non-empty and will pass the emptiness check, even though it might semantically represent an "empty" or "zero" value. Similarly, numeric zero (0) becomes "0" which is non-empty. Consider type-aware validation or document this behavior clearly so test authors understand that "empty" means the string representation is empty, not that the value is a zero/false/nil value.
		for field, expected := range step.JSONFields {
			val, ok := parsed[field]
			require.True(t, ok, "JSON output of '%s' missing field %q", step.Name, field)
			if expected != "" {
				require.Equal(t, expected, fmt.Sprintf("%v", val),
					"JSON field %q of '%s' has unexpected value", field, step.Name)
			} else {
				require.NotEmpty(t, fmt.Sprintf("%v", val),
					"JSON field %q of '%s' is empty", field, step.Name)
			}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +24 to +25
s.registerCleanup(t, "environment delete {{.env_id}} --force", state)
s.registerCleanup(t, "kafka cluster delete {{.cluster_id}} --force --environment {{.env_id}}", state)
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cleanup functions are registered in the wrong order. The comment says "Cleanup in LIFO order: delete cluster first, then environment" but the registration order has environment deletion first (line 24), then cluster deletion (line 25). Since t.Cleanup() executes in LIFO order (last registered runs first), this will attempt to delete the environment before the cluster, which will likely fail because the cluster still exists. The registration order should be reversed: register environment cleanup first, then cluster cleanup, so cluster is deleted before environment.

Suggested change
s.registerCleanup(t, "environment delete {{.env_id}} --force", state)
s.registerCleanup(t, "kafka cluster delete {{.cluster_id}} --force --environment {{.env_id}}", state)
s.registerCleanup(t, "kafka cluster delete {{.cluster_id}} --force --environment {{.env_id}}", state)
s.registerCleanup(t, "environment delete {{.env_id}} --force", state)

Copilot uses AI. Check for mistakes.
Comment on lines +21 to +22
s.registerCleanup(t, "iam service-account delete {{.sa_id}} --force", state)
s.registerCleanup(t, "api-key delete {{.api_key_id}} --force", state)
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cleanup functions are registered in the wrong order. The comment says "Cleanup in LIFO order: delete API key first, then service account" but the registration order has service account deletion first (line 21), then API key deletion (line 22). Since t.Cleanup() executes in LIFO order (last registered runs first), this will attempt to delete the service account before the API key, which will likely fail because the API key still references the service account. The registration order should be reversed: register service account cleanup first, then API key cleanup, so API key is deleted before service account.

Suggested change
s.registerCleanup(t, "iam service-account delete {{.sa_id}} --force", state)
s.registerCleanup(t, "api-key delete {{.api_key_id}} --force", state)
s.registerCleanup(t, "api-key delete {{.api_key_id}} --force", state)
s.registerCleanup(t, "iam service-account delete {{.sa_id}} --force", state)

Copilot uses AI. Check for mistakes.
Comment on lines +47 to +80
// shellSplit splits a command string into arguments, respecting double and single quotes.
// Unlike strings.Fields, it treats quoted substrings as single arguments.
func shellSplit(s string) []string {
var args []string
var current strings.Builder
inQuote := false
var quoteChar byte

for i := 0; i < len(s); i++ {
c := s[i]
switch {
case inQuote:
if c == quoteChar {
inQuote = false
} else {
current.WriteByte(c)
}
case c == '"' || c == '\'':
inQuote = true
quoteChar = c
case c == ' ' || c == '\t':
if current.Len() > 0 {
args = append(args, current.String())
current.Reset()
}
default:
current.WriteByte(c)
}
}
if current.Len() > 0 {
args = append(args, current.String())
}
return args
}
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The shellSplit function does not handle escaped characters (backslashes) within quotes or outside quotes. For example, \" or \' won't be properly parsed, and neither will escaped backslashes like \\. This can cause issues when command arguments contain literal quotes or backslashes. The existing test suite already uses github.com/google/shlex which handles these cases properly. Consider using shlex instead of this custom implementation.

Copilot uses AI. Check for mistakes.
Comment on lines +104 to +110
### 3. Register cleanup

Always register cleanup **before** creating resources (LIFO execution order):

```go
s.registerCleanup(t, "resource delete {{.resource_id}} --force", state)
```
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation states "Always register cleanup before creating resources (LIFO execution order)" but this is misleading. While the cleanup should be registered early in the test, the key point is that when you have dependencies, you must register them in reverse dependency order because t.Cleanup() executes in LIFO order. The current wording could be misinterpreted to mean "register all cleanups before any resource creation" when what matters is the registration order relative to other cleanups. Consider clarifying: "Register cleanup handlers in reverse dependency order (parent resources before child resources) so that t.Cleanup() LIFO execution deletes children before parents."

Copilot uses AI. Check for mistakes.
import (
"encoding/json"
"fmt"
"math/rand"
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The math/rand package is used without seeding, which means the random number generator will use the same seed each time in Go versions prior to 1.20. While Go 1.20+ auto-seeds the global random number generator, this can still lead to predictable resource names that may cause conflicts if tests run concurrently or if multiple test runs happen in quick succession. Consider using math/rand/v2 or explicitly seeding with time.Now().UnixNano() for better randomness, or switch to using crypto/rand for cryptographically secure random numbers.

Copilot uses AI. Check for mistakes.
Comment on lines +133 to +135
build-for-live-test:
go build -ldflags="-s -w -X main.commit=00000000 -X main.date=1970-01-01T00:00:00Z -X main.disableUpdates=true" \
-o test/live/bin/confluent ./cmd/confluent
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The build-for-live-test target doesn't handle Windows platform differences. The existing integration test build has separate targets for Windows (build-for-integration-test-windows at line 106) that appends .exe to the binary name. The live test binary path in live_test.go (line 23) references just test/live/bin/confluent without the Windows .exe extension handling. While the SetupSuite method adds .exe for Windows (lines 113-115 in live_test.go), the Makefile should also have a Windows-specific build target or conditional logic to build test/live/bin/confluent.exe on Windows for consistency with the integration test pattern.

Suggested change
build-for-live-test:
go build -ldflags="-s -w -X main.commit=00000000 -X main.date=1970-01-01T00:00:00Z -X main.disableUpdates=true" \
-o test/live/bin/confluent ./cmd/confluent
build-for-live-test:
ifneq "" "$(findstring NT,$(shell uname))" # windows
go build -ldflags="-s -w -X main.commit=00000000 -X main.date=1970-01-01T00:00:00Z -X main.disableUpdates=true" \
-o test/live/bin/confluent.exe ./cmd/confluent
else
go build -ldflags="-s -w -X main.commit=00000000 -X main.date=1970-01-01T00:00:00Z -X main.disableUpdates=true" \
-o test/live/bin/confluent ./cmd/confluent
endif

Copilot uses AI. Check for mistakes.
RC=$?
if [ $RC -ne 0 ]; then
echo "Live tests failed, sending Slack notification..."
curl -X POST -H "Content-type: application/json" --data "{}" "$SLACK_WEBHOOK_URL"
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Slack notification sends an empty JSON object as the message payload. This means the webhook will receive {} which likely won't produce a meaningful notification message. The payload should include information about the test failure, such as the job name, build link, or error details. For example: --data '{"text":"CLI live tests failed in job $SEMAPHORE_JOB_NAME. See: $SEMAPHORE_WORKFLOW_URL"}'

Suggested change
curl -X POST -H "Content-type: application/json" --data "{}" "$SLACK_WEBHOOK_URL"
curl -X POST -H "Content-type: application/json" --data "{\"text\":\"CLI live tests failed in job $SEMAPHORE_JOB_NAME. See: $SEMAPHORE_WORKFLOW_URL\"}" "$SLACK_WEBHOOK_URL"

Copilot uses AI. Check for mistakes.
Comment on lines +98 to +122
func (s *CLILiveTestSuite) waitForCondition(t *testing.T, argsTemplate string, state *LiveTestState,
condition func(output string) bool, interval, timeout time.Duration) string {
t.Helper()

deadline := time.Now().Add(timeout)
args := substituteStateVars(t, argsTemplate, state)
env := buildCommandEnv([]string{homeEnvVar(state.homeDir)})

var lastOutput string
for {
cmd := exec.Command(s.binPath, shellSplit(args)...)
cmd.Env = env
out, _ := cmd.CombinedOutput()
lastOutput = string(out)

if condition(lastOutput) {
return lastOutput
}
if time.Now().After(deadline) {
t.Fatalf("waitForCondition timed out after %s — last output:\n%s", timeout, lastOutput)
}
t.Logf("Condition not met, retrying in %s...", interval)
time.Sleep(interval)
}
}
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The waitForCondition function lacks proper timeout context management. While it checks time.Now().After(deadline), it doesn't cancel the running command when the timeout is reached. If the CLI command hangs or takes an extremely long time, this could cause the test to wait unnecessarily even after the deadline has passed. Consider using context.WithTimeout and passing the context to cmd.Run() or cmd.Start() to ensure the command is properly terminated when the timeout expires.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants