From bf76876ea1db708fb12714b5e6149bbc36239a3f Mon Sep 17 00:00:00 2001 From: Grigory Panov Date: Thu, 22 Oct 2026 13:28:21 +0200 Subject: [PATCH 1/5] Add acceptance tests for aitools list No acceptance test ran `aitools list`, and the aitools tests set DATABRICKS_SKILLS_REF, so the compat manifest and the latest skills release lookup were never covered. Add DATABRICKS_SKILLS_API_URL so the test server can replace api.github.com, and cover an explicit ref, a pinned compat entry, latest with a successful lookup, and latest with a 403 from the lookup. Co-authored-by: Isaac --- .../aitools/list/explicit-ref/out.test.toml | 1 + .../cmd/aitools/list/explicit-ref/output.txt | 33 ++++++++++ .../cmd/aitools/list/explicit-ref/script | 11 ++++ .../cmd/aitools/list/explicit-ref/test.toml | 2 + .../list/latest-lookup-fails/out.test.toml | 1 + .../list/latest-lookup-fails/output.txt | 13 ++++ .../aitools/list/latest-lookup-fails/script | 17 ++++++ .../list/latest-lookup-fails/test.toml | 5 ++ .../cmd/aitools/list/latest/out.test.toml | 1 + acceptance/cmd/aitools/list/latest/output.txt | 25 ++++++++ acceptance/cmd/aitools/list/latest/script | 17 ++++++ acceptance/cmd/aitools/list/latest/test.toml | 3 + .../cmd/aitools/list/pinned/out.test.toml | 1 + acceptance/cmd/aitools/list/pinned/output.txt | 21 +++++++ acceptance/cmd/aitools/list/pinned/script | 17 ++++++ acceptance/cmd/aitools/list/test.toml | 60 +++++++++++++++++++ libs/aitools/installer/installer.go | 11 +++- libs/aitools/installer/installer_test.go | 16 +++++ 18 files changed, 254 insertions(+), 1 deletion(-) create mode 100644 acceptance/cmd/aitools/list/explicit-ref/out.test.toml create mode 100644 acceptance/cmd/aitools/list/explicit-ref/output.txt create mode 100644 acceptance/cmd/aitools/list/explicit-ref/script create mode 100644 acceptance/cmd/aitools/list/explicit-ref/test.toml create mode 100644 acceptance/cmd/aitools/list/latest-lookup-fails/out.test.toml create mode 100644 acceptance/cmd/aitools/list/latest-lookup-fails/output.txt create mode 100644 acceptance/cmd/aitools/list/latest-lookup-fails/script create mode 100644 acceptance/cmd/aitools/list/latest-lookup-fails/test.toml create mode 100644 acceptance/cmd/aitools/list/latest/out.test.toml create mode 100644 acceptance/cmd/aitools/list/latest/output.txt create mode 100644 acceptance/cmd/aitools/list/latest/script create mode 100644 acceptance/cmd/aitools/list/latest/test.toml create mode 100644 acceptance/cmd/aitools/list/pinned/out.test.toml create mode 100644 acceptance/cmd/aitools/list/pinned/output.txt create mode 100644 acceptance/cmd/aitools/list/pinned/script create mode 100644 acceptance/cmd/aitools/list/test.toml diff --git a/acceptance/cmd/aitools/list/explicit-ref/out.test.toml b/acceptance/cmd/aitools/list/explicit-ref/out.test.toml new file mode 100644 index 00000000000..18b1a88417e --- /dev/null +++ b/acceptance/cmd/aitools/list/explicit-ref/out.test.toml @@ -0,0 +1 @@ +Cloud = false diff --git a/acceptance/cmd/aitools/list/explicit-ref/output.txt b/acceptance/cmd/aitools/list/explicit-ref/output.txt new file mode 100644 index 00000000000..bc6e6e11d79 --- /dev/null +++ b/acceptance/cmd/aitools/list/explicit-ref/output.txt @@ -0,0 +1,33 @@ + +>>> [CLI] aitools list +Available raw skill directories (test-ref): + + NAME VERSION INSTALLED + test-skill v1.0.0 not installed + +0/1 raw skill directories installed (global) + +>>> [CLI] aitools list --output json +{ + "release": "test-ref", + "skills": [ + { + "name": "test-skill", + "latest_version": "1.0.0", + "experimental": false, + "installed": {} + } + ] +} + +=== Only the manifest is fetched: no compat manifest, no latest-release lookup + +>>> print_requests.py --get +{ + "method": "GET", + "path": "/test-ref/manifest.json" +} +{ + "method": "GET", + "path": "/test-ref/manifest.json" +} diff --git a/acceptance/cmd/aitools/list/explicit-ref/script b/acceptance/cmd/aitools/list/explicit-ref/script new file mode 100644 index 00000000000..6abafefe330 --- /dev/null +++ b/acceptance/cmd/aitools/list/explicit-ref/script @@ -0,0 +1,11 @@ +# Isolate HOME so parallel aitools tests don't race on a shared ~/.databricks. +sethome home +# Agent detection needs ~/.claude; prefer USERPROFILE on Windows. +mkdir -p "${USERPROFILE:-$HOME}/.claude" + +trace $CLI aitools list +# Agent detection depends on the binaries on PATH, so keep only the skills. +trace $CLI aitools list --output json | jq '{release, skills}' + +title "Only the manifest is fetched: no compat manifest, no latest-release lookup\n" +trace print_requests.py --get diff --git a/acceptance/cmd/aitools/list/explicit-ref/test.toml b/acceptance/cmd/aitools/list/explicit-ref/test.toml new file mode 100644 index 00000000000..9824d217206 --- /dev/null +++ b/acceptance/cmd/aitools/list/explicit-ref/test.toml @@ -0,0 +1,2 @@ +# DATABRICKS_SKILLS_REF skips the compat manifest and the latest-release lookup. +Env.DATABRICKS_SKILLS_REF = "test-ref" diff --git a/acceptance/cmd/aitools/list/latest-lookup-fails/out.test.toml b/acceptance/cmd/aitools/list/latest-lookup-fails/out.test.toml new file mode 100644 index 00000000000..18b1a88417e --- /dev/null +++ b/acceptance/cmd/aitools/list/latest-lookup-fails/out.test.toml @@ -0,0 +1 @@ +Cloud = false diff --git a/acceptance/cmd/aitools/list/latest-lookup-fails/output.txt b/acceptance/cmd/aitools/list/latest-lookup-fails/output.txt new file mode 100644 index 00000000000..329250eacf0 --- /dev/null +++ b/acceptance/cmd/aitools/list/latest-lookup-fails/output.txt @@ -0,0 +1,13 @@ + +>>> errcode [CLI] aitools list --output json +Error: resolve latest skills release: HTTP 403 fetching latest skills release + +Exit code: 1 + +=== The lookup fails, so the manifest is never fetched + +>>> print_requests.py --get +{ + "method": "GET", + "path": "/repos/databricks/databricks-agent-skills/releases/latest" +} diff --git a/acceptance/cmd/aitools/list/latest-lookup-fails/script b/acceptance/cmd/aitools/list/latest-lookup-fails/script new file mode 100644 index 00000000000..5f3f238fbf9 --- /dev/null +++ b/acceptance/cmd/aitools/list/latest-lookup-fails/script @@ -0,0 +1,17 @@ +# Isolate HOME so parallel aitools tests don't race on a shared ~/.databricks. +sethome home +# Agent detection needs ~/.claude; prefer USERPROFILE on Windows. +mkdir -p "${USERPROFILE:-$HOME}/.claude" + +# A fresh cached compat manifest replaces the remote one. Dev builds use its +# highest entry. +mkdir -p "$DATABRICKS_CACHE_DIR" +cat > "$DATABRICKS_CACHE_DIR/compat-manifest.json" <<'JSON' +{"1.0.0": {"appkit": "0.84.0", "skills": "latest"}} +JSON + +# Agent detection depends on the binaries on PATH, so keep only the skills. +trace errcode $CLI aitools list --output json | jq '{release, skills}' + +title "The lookup fails, so the manifest is never fetched\n" +trace print_requests.py --get diff --git a/acceptance/cmd/aitools/list/latest-lookup-fails/test.toml b/acceptance/cmd/aitools/list/latest-lookup-fails/test.toml new file mode 100644 index 00000000000..d17526f92a8 --- /dev/null +++ b/acceptance/cmd/aitools/list/latest-lookup-fails/test.toml @@ -0,0 +1,5 @@ +# The unauthenticated GitHub API answers 403 when the per-IP rate limit is hit. +[[Server]] +Pattern = "GET /repos/databricks/databricks-agent-skills/releases/latest" +Response.StatusCode = 403 +Response.Body = '{"message": "API rate limit exceeded"}' diff --git a/acceptance/cmd/aitools/list/latest/out.test.toml b/acceptance/cmd/aitools/list/latest/out.test.toml new file mode 100644 index 00000000000..18b1a88417e --- /dev/null +++ b/acceptance/cmd/aitools/list/latest/out.test.toml @@ -0,0 +1 @@ +Cloud = false diff --git a/acceptance/cmd/aitools/list/latest/output.txt b/acceptance/cmd/aitools/list/latest/output.txt new file mode 100644 index 00000000000..ccd8b3c8fbf --- /dev/null +++ b/acceptance/cmd/aitools/list/latest/output.txt @@ -0,0 +1,25 @@ + +>>> [CLI] aitools list --output json +{ + "release": "0.2.33", + "skills": [ + { + "name": "test-skill", + "latest_version": "1.0.33", + "experimental": false, + "installed": {} + } + ] +} + +=== The latest-release lookup picks the manifest version + +>>> print_requests.py --get +{ + "method": "GET", + "path": "/repos/databricks/databricks-agent-skills/releases/latest" +} +{ + "method": "GET", + "path": "/v0.2.33/manifest.json" +} diff --git a/acceptance/cmd/aitools/list/latest/script b/acceptance/cmd/aitools/list/latest/script new file mode 100644 index 00000000000..e042da7deb0 --- /dev/null +++ b/acceptance/cmd/aitools/list/latest/script @@ -0,0 +1,17 @@ +# Isolate HOME so parallel aitools tests don't race on a shared ~/.databricks. +sethome home +# Agent detection needs ~/.claude; prefer USERPROFILE on Windows. +mkdir -p "${USERPROFILE:-$HOME}/.claude" + +# A fresh cached compat manifest replaces the remote one. Dev builds use its +# highest entry. +mkdir -p "$DATABRICKS_CACHE_DIR" +cat > "$DATABRICKS_CACHE_DIR/compat-manifest.json" <<'JSON' +{"1.0.0": {"appkit": "0.84.0", "skills": "latest"}} +JSON + +# Agent detection depends on the binaries on PATH, so keep only the skills. +trace $CLI aitools list --output json | jq '{release, skills}' + +title "The latest-release lookup picks the manifest version\n" +trace print_requests.py --get diff --git a/acceptance/cmd/aitools/list/latest/test.toml b/acceptance/cmd/aitools/list/latest/test.toml new file mode 100644 index 00000000000..4ab7e64bf67 --- /dev/null +++ b/acceptance/cmd/aitools/list/latest/test.toml @@ -0,0 +1,3 @@ +[[Server]] +Pattern = "GET /repos/databricks/databricks-agent-skills/releases/latest" +Response.Body = '{"tag_name": "v0.2.33"}' diff --git a/acceptance/cmd/aitools/list/pinned/out.test.toml b/acceptance/cmd/aitools/list/pinned/out.test.toml new file mode 100644 index 00000000000..18b1a88417e --- /dev/null +++ b/acceptance/cmd/aitools/list/pinned/out.test.toml @@ -0,0 +1 @@ +Cloud = false diff --git a/acceptance/cmd/aitools/list/pinned/output.txt b/acceptance/cmd/aitools/list/pinned/output.txt new file mode 100644 index 00000000000..4097713389c --- /dev/null +++ b/acceptance/cmd/aitools/list/pinned/output.txt @@ -0,0 +1,21 @@ + +>>> [CLI] aitools list --output json +{ + "release": "0.2.32", + "skills": [ + { + "name": "test-skill", + "latest_version": "1.0.32", + "experimental": false, + "installed": {} + } + ] +} + +=== A pinned version skips the latest-release lookup + +>>> print_requests.py --get +{ + "method": "GET", + "path": "/v0.2.32/manifest.json" +} diff --git a/acceptance/cmd/aitools/list/pinned/script b/acceptance/cmd/aitools/list/pinned/script new file mode 100644 index 00000000000..ae64cd08ba9 --- /dev/null +++ b/acceptance/cmd/aitools/list/pinned/script @@ -0,0 +1,17 @@ +# Isolate HOME so parallel aitools tests don't race on a shared ~/.databricks. +sethome home +# Agent detection needs ~/.claude; prefer USERPROFILE on Windows. +mkdir -p "${USERPROFILE:-$HOME}/.claude" + +# A fresh cached compat manifest replaces the remote one. Dev builds use its +# highest entry. +mkdir -p "$DATABRICKS_CACHE_DIR" +cat > "$DATABRICKS_CACHE_DIR/compat-manifest.json" <<'JSON' +{"1.0.0": {"appkit": "0.84.0", "skills": "0.2.32"}} +JSON + +# Agent detection depends on the binaries on PATH, so keep only the skills. +trace $CLI aitools list --output json | jq '{release, skills}' + +title "A pinned version skips the latest-release lookup\n" +trace print_requests.py --get diff --git a/acceptance/cmd/aitools/list/test.toml b/acceptance/cmd/aitools/list/test.toml new file mode 100644 index 00000000000..975328aeed4 --- /dev/null +++ b/acceptance/cmd/aitools/list/test.toml @@ -0,0 +1,60 @@ +# The mock server replaces raw.githubusercontent.com (skills manifest) and +# api.github.com (latest skills release lookup). +Env.DATABRICKS_SKILLS_BASE_URL = "$DATABRICKS_HOST" +Env.DATABRICKS_SKILLS_API_URL = "$DATABRICKS_HOST" +RecordRequests = true + +Ignore = [ + "home", +] + +[[Server]] +Pattern = "GET /test-ref/manifest.json" +Response.Body = ''' +{ + "version": "2", + "updated_at": "2026-01-01T00:00:00Z", + "skills": { + "test-skill": { + "version": "1.0.0", + "description": "Test skill", + "files": ["SKILL.md"], + "repo_dir": "skills" + } + } +} +''' + +[[Server]] +Pattern = "GET /v0.2.32/manifest.json" +Response.Body = ''' +{ + "version": "2", + "updated_at": "2026-01-01T00:00:00Z", + "skills": { + "test-skill": { + "version": "1.0.32", + "description": "Test skill", + "files": ["SKILL.md"], + "repo_dir": "skills" + } + } +} +''' + +[[Server]] +Pattern = "GET /v0.2.33/manifest.json" +Response.Body = ''' +{ + "version": "2", + "updated_at": "2026-01-01T00:00:00Z", + "skills": { + "test-skill": { + "version": "1.0.33", + "description": "Test skill", + "files": ["SKILL.md"], + "repo_dir": "skills" + } + } +} +''' diff --git a/libs/aitools/installer/installer.go b/libs/aitools/installer/installer.go index 0ca33012f0f..4548c93ac58 100644 --- a/libs/aitools/installer/installer.go +++ b/libs/aitools/installer/installer.go @@ -140,8 +140,17 @@ func resolveLatestSkillsReleaseRef(ctx context.Context, allowFallback bool) (ref return "", false, fmt.Errorf("resolve latest skills release: %w", err) } +// getSkillsAPIURL returns the GitHub API base URL for the latest-release lookup. +// DATABRICKS_SKILLS_API_URL overrides it for acceptance tests. +func getSkillsAPIURL(ctx context.Context) string { + if base := env.Get(ctx, "DATABRICKS_SKILLS_API_URL"); base != "" { + return strings.TrimRight(base, "/") + } + return "https://api.github.com" +} + func fetchLatestSkillsReleaseRef(ctx context.Context) (string, error) { - url := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", skillsRepoOwner, skillsRepoName) + url := fmt.Sprintf("%s/repos/%s/%s/releases/latest", getSkillsAPIURL(ctx), skillsRepoOwner, skillsRepoName) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return "", fmt.Errorf("failed to create request: %w", err) diff --git a/libs/aitools/installer/installer_test.go b/libs/aitools/installer/installer_test.go index 8c395fad84a..41fe9406ef8 100644 --- a/libs/aitools/installer/installer_test.go +++ b/libs/aitools/installer/installer_test.go @@ -1398,6 +1398,22 @@ func TestGetSkillsRefLatestReleaseLookupFails(t *testing.T) { assert.False(t, explicit) } +func TestFetchLatestSkillsReleaseRefUsesAPIURLOverride(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/repos/databricks/databricks-agent-skills/releases/latest" { + http.NotFound(w, r) + return + } + _, _ = w.Write([]byte(`{"tag_name": "v0.2.33"}`)) + })) + t.Cleanup(srv.Close) + t.Setenv("DATABRICKS_SKILLS_API_URL", srv.URL+"/") + + ref, err := fetchLatestSkillsReleaseRef(t.Context()) + require.NoError(t, err) + assert.Equal(t, "v0.2.33", ref) +} + func TestFetchSkillsManifestWithFallback(t *testing.T) { const missingRef = "v9.9.9" setBuildVersion(t, "1.22.0") From 841d405f470d2fe40674d15b4c81aef747d37bb3 Mon Sep 17 00:00:00 2001 From: Grigory Panov Date: Thu, 22 Oct 2026 13:37:00 +0200 Subject: [PATCH 2/5] Address review findings in aitools list acceptance tests - Reuse DATABRICKS_CLI_GITHUB_API_URL (from libs/versioncheck) for the latest skills release lookup instead of a new env var, and drop the unit test that only covered the new var. - Run acceptance/cmd/aitools in the test-exp-aitools CI target. - Commit the compat manifests, move shared setup and the jq allow-list (now with summary) to script.prepare, and serve the skills manifest from one {ref} stub. - Assert the lookup failure with musterr, and pin the compat cache settings so the shell environment does not change the result. Co-authored-by: Isaac --- Taskfile.yml | 3 +- .../cmd/aitools/list/explicit-ref/output.txt | 14 ++++++- .../cmd/aitools/list/explicit-ref/script | 10 ++--- .../latest-lookup-fails/compat-manifest.json | 1 + .../list/latest-lookup-fails/output.txt | 4 +- .../aitools/list/latest-lookup-fails/script | 18 +++----- .../aitools/list/latest/compat-manifest.json | 1 + acceptance/cmd/aitools/list/latest/output.txt | 14 ++++++- acceptance/cmd/aitools/list/latest/script | 16 ++----- .../aitools/list/pinned/compat-manifest.json | 1 + acceptance/cmd/aitools/list/pinned/output.txt | 14 ++++++- acceptance/cmd/aitools/list/pinned/script | 16 ++----- acceptance/cmd/aitools/list/script.prepare | 21 ++++++++++ acceptance/cmd/aitools/list/test.toml | 42 +++---------------- libs/aitools/installer/installer.go | 19 ++++++--- libs/aitools/installer/installer_test.go | 16 ------- tools/testmask/targets_test.go | 7 ++++ 17 files changed, 103 insertions(+), 114 deletions(-) create mode 100644 acceptance/cmd/aitools/list/latest-lookup-fails/compat-manifest.json create mode 100644 acceptance/cmd/aitools/list/latest/compat-manifest.json create mode 100644 acceptance/cmd/aitools/list/pinned/compat-manifest.json create mode 100644 acceptance/cmd/aitools/list/script.prepare diff --git a/Taskfile.yml b/Taskfile.yml index b41178fbe61..d2395d9bb3d 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -648,6 +648,7 @@ tasks: - libs/aitools/** - experimental/aitools/** - acceptance/apps/** + - acceptance/cmd/aitools/** - acceptance/experimental/aitools/** - "{{.EMBED_SOURCES}}" cmds: @@ -662,7 +663,7 @@ tasks: --format ${GOTESTSUM_FORMAT:-pkgname-and-test-fails} \ --no-summary=skipped \ --packages ./acceptance/... \ - -- -timeout=${LOCAL_TIMEOUT:-60m} -run "TestAccept/(apps|experimental/aitools)" + -- -timeout=${LOCAL_TIMEOUT:-60m} -run "TestAccept/(apps|cmd/aitools|experimental/aitools)" test-exp-ssh: desc: Run experimental SSH unit and acceptance tests diff --git a/acceptance/cmd/aitools/list/explicit-ref/output.txt b/acceptance/cmd/aitools/list/explicit-ref/output.txt index bc6e6e11d79..144d0f7b4f5 100644 --- a/acceptance/cmd/aitools/list/explicit-ref/output.txt +++ b/acceptance/cmd/aitools/list/explicit-ref/output.txt @@ -17,10 +17,20 @@ Available raw skill directories (test-ref): "experimental": false, "installed": {} } - ] + ], + "summary": { + "global": { + "installed": 0, + "total": 1 + }, + "project": { + "installed": 0, + "total": 1 + } + } } -=== Only the manifest is fetched: no compat manifest, no latest-release lookup +=== Only the skills manifest reaches the mock server: no latest-release lookup >>> print_requests.py --get { diff --git a/acceptance/cmd/aitools/list/explicit-ref/script b/acceptance/cmd/aitools/list/explicit-ref/script index 6abafefe330..f348eb7e97f 100644 --- a/acceptance/cmd/aitools/list/explicit-ref/script +++ b/acceptance/cmd/aitools/list/explicit-ref/script @@ -1,11 +1,7 @@ -# Isolate HOME so parallel aitools tests don't race on a shared ~/.databricks. -sethome home -# Agent detection needs ~/.claude; prefer USERPROFILE on Windows. -mkdir -p "${USERPROFILE:-$HOME}/.claude" +setup_list_test trace $CLI aitools list -# Agent detection depends on the binaries on PATH, so keep only the skills. -trace $CLI aitools list --output json | jq '{release, skills}' +trace $CLI aitools list --output json | list_fields -title "Only the manifest is fetched: no compat manifest, no latest-release lookup\n" +title "Only the skills manifest reaches the mock server: no latest-release lookup\n" trace print_requests.py --get diff --git a/acceptance/cmd/aitools/list/latest-lookup-fails/compat-manifest.json b/acceptance/cmd/aitools/list/latest-lookup-fails/compat-manifest.json new file mode 100644 index 00000000000..eaa0f4668b3 --- /dev/null +++ b/acceptance/cmd/aitools/list/latest-lookup-fails/compat-manifest.json @@ -0,0 +1 @@ +{"1.0.0": {"appkit": "0.84.0", "skills": "latest"}} diff --git a/acceptance/cmd/aitools/list/latest-lookup-fails/output.txt b/acceptance/cmd/aitools/list/latest-lookup-fails/output.txt index 329250eacf0..ff293d47269 100644 --- a/acceptance/cmd/aitools/list/latest-lookup-fails/output.txt +++ b/acceptance/cmd/aitools/list/latest-lookup-fails/output.txt @@ -1,9 +1,7 @@ ->>> errcode [CLI] aitools list --output json +>>> musterr [CLI] aitools list --output json Error: resolve latest skills release: HTTP 403 fetching latest skills release -Exit code: 1 - === The lookup fails, so the manifest is never fetched >>> print_requests.py --get diff --git a/acceptance/cmd/aitools/list/latest-lookup-fails/script b/acceptance/cmd/aitools/list/latest-lookup-fails/script index 5f3f238fbf9..ce6c38c6cc8 100644 --- a/acceptance/cmd/aitools/list/latest-lookup-fails/script +++ b/acceptance/cmd/aitools/list/latest-lookup-fails/script @@ -1,17 +1,9 @@ -# Isolate HOME so parallel aitools tests don't race on a shared ~/.databricks. -sethome home -# Agent detection needs ~/.claude; prefer USERPROFILE on Windows. -mkdir -p "${USERPROFILE:-$HOME}/.claude" +setup_list_test +use_compat_manifest -# A fresh cached compat manifest replaces the remote one. Dev builds use its -# highest entry. -mkdir -p "$DATABRICKS_CACHE_DIR" -cat > "$DATABRICKS_CACHE_DIR/compat-manifest.json" <<'JSON' -{"1.0.0": {"appkit": "0.84.0", "skills": "latest"}} -JSON - -# Agent detection depends on the binaries on PATH, so keep only the skills. -trace errcode $CLI aitools list --output json | jq '{release, skills}' +# Without an embedded fallback, a failed lookup fails the command. This is the +# CLI 1.22.0 behavior that #7159 mitigates with a pinned compat entry. +trace musterr $CLI aitools list --output json title "The lookup fails, so the manifest is never fetched\n" trace print_requests.py --get diff --git a/acceptance/cmd/aitools/list/latest/compat-manifest.json b/acceptance/cmd/aitools/list/latest/compat-manifest.json new file mode 100644 index 00000000000..eaa0f4668b3 --- /dev/null +++ b/acceptance/cmd/aitools/list/latest/compat-manifest.json @@ -0,0 +1 @@ +{"1.0.0": {"appkit": "0.84.0", "skills": "latest"}} diff --git a/acceptance/cmd/aitools/list/latest/output.txt b/acceptance/cmd/aitools/list/latest/output.txt index ccd8b3c8fbf..3e8dbfe030a 100644 --- a/acceptance/cmd/aitools/list/latest/output.txt +++ b/acceptance/cmd/aitools/list/latest/output.txt @@ -5,11 +5,21 @@ "skills": [ { "name": "test-skill", - "latest_version": "1.0.33", + "latest_version": "1.0.0", "experimental": false, "installed": {} } - ] + ], + "summary": { + "global": { + "installed": 0, + "total": 1 + }, + "project": { + "installed": 0, + "total": 1 + } + } } === The latest-release lookup picks the manifest version diff --git a/acceptance/cmd/aitools/list/latest/script b/acceptance/cmd/aitools/list/latest/script index e042da7deb0..c39f60a011a 100644 --- a/acceptance/cmd/aitools/list/latest/script +++ b/acceptance/cmd/aitools/list/latest/script @@ -1,17 +1,7 @@ -# Isolate HOME so parallel aitools tests don't race on a shared ~/.databricks. -sethome home -# Agent detection needs ~/.claude; prefer USERPROFILE on Windows. -mkdir -p "${USERPROFILE:-$HOME}/.claude" +setup_list_test +use_compat_manifest -# A fresh cached compat manifest replaces the remote one. Dev builds use its -# highest entry. -mkdir -p "$DATABRICKS_CACHE_DIR" -cat > "$DATABRICKS_CACHE_DIR/compat-manifest.json" <<'JSON' -{"1.0.0": {"appkit": "0.84.0", "skills": "latest"}} -JSON - -# Agent detection depends on the binaries on PATH, so keep only the skills. -trace $CLI aitools list --output json | jq '{release, skills}' +trace $CLI aitools list --output json | list_fields title "The latest-release lookup picks the manifest version\n" trace print_requests.py --get diff --git a/acceptance/cmd/aitools/list/pinned/compat-manifest.json b/acceptance/cmd/aitools/list/pinned/compat-manifest.json new file mode 100644 index 00000000000..deb1eb20945 --- /dev/null +++ b/acceptance/cmd/aitools/list/pinned/compat-manifest.json @@ -0,0 +1 @@ +{"1.0.0": {"appkit": "0.84.0", "skills": "0.2.32"}} diff --git a/acceptance/cmd/aitools/list/pinned/output.txt b/acceptance/cmd/aitools/list/pinned/output.txt index 4097713389c..329eab653cb 100644 --- a/acceptance/cmd/aitools/list/pinned/output.txt +++ b/acceptance/cmd/aitools/list/pinned/output.txt @@ -5,11 +5,21 @@ "skills": [ { "name": "test-skill", - "latest_version": "1.0.32", + "latest_version": "1.0.0", "experimental": false, "installed": {} } - ] + ], + "summary": { + "global": { + "installed": 0, + "total": 1 + }, + "project": { + "installed": 0, + "total": 1 + } + } } === A pinned version skips the latest-release lookup diff --git a/acceptance/cmd/aitools/list/pinned/script b/acceptance/cmd/aitools/list/pinned/script index ae64cd08ba9..62f0cd3a522 100644 --- a/acceptance/cmd/aitools/list/pinned/script +++ b/acceptance/cmd/aitools/list/pinned/script @@ -1,17 +1,7 @@ -# Isolate HOME so parallel aitools tests don't race on a shared ~/.databricks. -sethome home -# Agent detection needs ~/.claude; prefer USERPROFILE on Windows. -mkdir -p "${USERPROFILE:-$HOME}/.claude" +setup_list_test +use_compat_manifest -# A fresh cached compat manifest replaces the remote one. Dev builds use its -# highest entry. -mkdir -p "$DATABRICKS_CACHE_DIR" -cat > "$DATABRICKS_CACHE_DIR/compat-manifest.json" <<'JSON' -{"1.0.0": {"appkit": "0.84.0", "skills": "0.2.32"}} -JSON - -# Agent detection depends on the binaries on PATH, so keep only the skills. -trace $CLI aitools list --output json | jq '{release, skills}' +trace $CLI aitools list --output json | list_fields title "A pinned version skips the latest-release lookup\n" trace print_requests.py --get diff --git a/acceptance/cmd/aitools/list/script.prepare b/acceptance/cmd/aitools/list/script.prepare new file mode 100644 index 00000000000..e77a2dbcecc --- /dev/null +++ b/acceptance/cmd/aitools/list/script.prepare @@ -0,0 +1,21 @@ +# setup_list_test isolates HOME, so parallel aitools tests don't race on a +# shared ~/.databricks. Agent detection needs ~/.claude; prefer USERPROFILE on +# Windows. +setup_list_test() { + sethome home + mkdir -p "${USERPROFILE:-$HOME}/.claude" +} + +# use_compat_manifest copies the test's compat-manifest.json into the CLI +# cache. A fresh cache file replaces the remote compat manifest, and dev builds +# use its highest entry. +use_compat_manifest() { + mkdir -p "$DATABRICKS_CACHE_DIR" + cp compat-manifest.json "$DATABRICKS_CACHE_DIR/compat-manifest.json" +} + +# list_fields keeps the fields of `aitools list --output json` that the tests +# assert. Agent detection depends on the binaries on PATH, so agents are left out. +list_fields() { + jq '{release, skills, summary}' +} diff --git a/acceptance/cmd/aitools/list/test.toml b/acceptance/cmd/aitools/list/test.toml index 975328aeed4..58b4b06c245 100644 --- a/acceptance/cmd/aitools/list/test.toml +++ b/acceptance/cmd/aitools/list/test.toml @@ -1,7 +1,11 @@ # The mock server replaces raw.githubusercontent.com (skills manifest) and # api.github.com (latest skills release lookup). Env.DATABRICKS_SKILLS_BASE_URL = "$DATABRICKS_HOST" -Env.DATABRICKS_SKILLS_API_URL = "$DATABRICKS_HOST" +Env.DATABRICKS_CLI_GITHUB_API_URL = "$DATABRICKS_HOST" +# Read the compat manifest from the cache file that a test writes, even if the +# shell sets other values. +Env.DATABRICKS_FORCE_EMBEDDED_COMPAT = "false" +Env.DATABRICKS_CACHE_ENABLED = "true" RecordRequests = true Ignore = [ @@ -9,7 +13,7 @@ Ignore = [ ] [[Server]] -Pattern = "GET /test-ref/manifest.json" +Pattern = "GET /{ref}/manifest.json" Response.Body = ''' { "version": "2", @@ -24,37 +28,3 @@ Response.Body = ''' } } ''' - -[[Server]] -Pattern = "GET /v0.2.32/manifest.json" -Response.Body = ''' -{ - "version": "2", - "updated_at": "2026-01-01T00:00:00Z", - "skills": { - "test-skill": { - "version": "1.0.32", - "description": "Test skill", - "files": ["SKILL.md"], - "repo_dir": "skills" - } - } -} -''' - -[[Server]] -Pattern = "GET /v0.2.33/manifest.json" -Response.Body = ''' -{ - "version": "2", - "updated_at": "2026-01-01T00:00:00Z", - "skills": { - "test-skill": { - "version": "1.0.33", - "description": "Test skill", - "files": ["SKILL.md"], - "repo_dir": "skills" - } - } -} -''' diff --git a/libs/aitools/installer/installer.go b/libs/aitools/installer/installer.go index 4548c93ac58..5d3489d742f 100644 --- a/libs/aitools/installer/installer.go +++ b/libs/aitools/installer/installer.go @@ -140,17 +140,24 @@ func resolveLatestSkillsReleaseRef(ctx context.Context, allowFallback bool) (ref return "", false, fmt.Errorf("resolve latest skills release: %w", err) } -// getSkillsAPIURL returns the GitHub API base URL for the latest-release lookup. -// DATABRICKS_SKILLS_API_URL overrides it for acceptance tests. -func getSkillsAPIURL(ctx context.Context) string { - if base := env.Get(ctx, "DATABRICKS_SKILLS_API_URL"); base != "" { +const ( + defaultGitHubAPIURL = "https://api.github.com" + + // gitHubAPIURLEnv overrides the GitHub API base URL, as in libs/versioncheck. + // Acceptance tests use it to point the latest-release lookup at the mock server. + gitHubAPIURLEnv = "DATABRICKS_CLI_GITHUB_API_URL" +) + +// getGitHubAPIURL returns the GitHub API base URL for the latest-release lookup. +func getGitHubAPIURL(ctx context.Context) string { + if base := env.Get(ctx, gitHubAPIURLEnv); base != "" { return strings.TrimRight(base, "/") } - return "https://api.github.com" + return defaultGitHubAPIURL } func fetchLatestSkillsReleaseRef(ctx context.Context) (string, error) { - url := fmt.Sprintf("%s/repos/%s/%s/releases/latest", getSkillsAPIURL(ctx), skillsRepoOwner, skillsRepoName) + url := fmt.Sprintf("%s/repos/%s/%s/releases/latest", getGitHubAPIURL(ctx), skillsRepoOwner, skillsRepoName) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return "", fmt.Errorf("failed to create request: %w", err) diff --git a/libs/aitools/installer/installer_test.go b/libs/aitools/installer/installer_test.go index 41fe9406ef8..8c395fad84a 100644 --- a/libs/aitools/installer/installer_test.go +++ b/libs/aitools/installer/installer_test.go @@ -1398,22 +1398,6 @@ func TestGetSkillsRefLatestReleaseLookupFails(t *testing.T) { assert.False(t, explicit) } -func TestFetchLatestSkillsReleaseRefUsesAPIURLOverride(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path != "/repos/databricks/databricks-agent-skills/releases/latest" { - http.NotFound(w, r) - return - } - _, _ = w.Write([]byte(`{"tag_name": "v0.2.33"}`)) - })) - t.Cleanup(srv.Close) - t.Setenv("DATABRICKS_SKILLS_API_URL", srv.URL+"/") - - ref, err := fetchLatestSkillsReleaseRef(t.Context()) - require.NoError(t, err) - assert.Equal(t, "v0.2.33", ref) -} - func TestFetchSkillsManifestWithFallback(t *testing.T) { const missingRef = "v9.9.9" setBuildVersion(t, "1.22.0") diff --git a/tools/testmask/targets_test.go b/tools/testmask/targets_test.go index b2b058fc59d..1366c1cfbab 100644 --- a/tools/testmask/targets_test.go +++ b/tools/testmask/targets_test.go @@ -38,6 +38,13 @@ func TestGetTargets(t *testing.T) { }, targets: []string{"test-exp-aitools"}, }, + { + name: "acceptance_cmd_aitools_triggers_aitools", + files: []string{ + "acceptance/cmd/aitools/list/latest/script", + }, + targets: []string{"test-exp-aitools"}, + }, { name: "sandbox", files: []string{ From b8cbd06e5a072642be4648876d33292bfbee0572 Mon Sep 17 00:00:00 2001 From: Grigory Panov Date: Thu, 22 Oct 2026 13:47:35 +0200 Subject: [PATCH 3/5] Share the GitHub API URL helper and fix the aitools CI pattern - Export versioncheck.GitHubAPIURL and use it for the skills release lookup, so DATABRICKS_CLI_GITHUB_API_URL behaves the same for both. - Use top-level alternation in the test-exp-aitools pattern; the parenthesized form selected only TestAccept/apps. - Clear DATABRICKS_SKILLS_REF in the list tests, drop the unused ~/.claude setup, and explain why the lookup failure is expected. Co-authored-by: Isaac --- Taskfile.yml | 2 +- .../aitools/list/latest-lookup-fails/script | 4 ++-- acceptance/cmd/aitools/list/script.prepare | 4 +--- acceptance/cmd/aitools/list/test.toml | 3 ++- libs/aitools/installer/installer.go | 19 ++--------------- libs/versioncheck/versioncheck.go | 21 ++++++++++++------- libs/versioncheck/versioncheck_test.go | 8 +++++++ 7 files changed, 29 insertions(+), 32 deletions(-) diff --git a/Taskfile.yml b/Taskfile.yml index d2395d9bb3d..e93b0cbde0a 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -663,7 +663,7 @@ tasks: --format ${GOTESTSUM_FORMAT:-pkgname-and-test-fails} \ --no-summary=skipped \ --packages ./acceptance/... \ - -- -timeout=${LOCAL_TIMEOUT:-60m} -run "TestAccept/(apps|cmd/aitools|experimental/aitools)" + -- -timeout=${LOCAL_TIMEOUT:-60m} -run "TestAccept/apps|TestAccept/cmd/aitools|TestAccept/experimental/aitools" test-exp-ssh: desc: Run experimental SSH unit and acceptance tests diff --git a/acceptance/cmd/aitools/list/latest-lookup-fails/script b/acceptance/cmd/aitools/list/latest-lookup-fails/script index ce6c38c6cc8..131f92568be 100644 --- a/acceptance/cmd/aitools/list/latest-lookup-fails/script +++ b/acceptance/cmd/aitools/list/latest-lookup-fails/script @@ -1,8 +1,8 @@ setup_list_test use_compat_manifest -# Without an embedded fallback, a failed lookup fails the command. This is the -# CLI 1.22.0 behavior that #7159 mitigates with a pinned compat entry. +# list does not fall back to the embedded skills version, so a failed +# latest-release lookup fails the command. trace musterr $CLI aitools list --output json title "The lookup fails, so the manifest is never fetched\n" diff --git a/acceptance/cmd/aitools/list/script.prepare b/acceptance/cmd/aitools/list/script.prepare index e77a2dbcecc..53efdf830f6 100644 --- a/acceptance/cmd/aitools/list/script.prepare +++ b/acceptance/cmd/aitools/list/script.prepare @@ -1,9 +1,7 @@ # setup_list_test isolates HOME, so parallel aitools tests don't race on a -# shared ~/.databricks. Agent detection needs ~/.claude; prefer USERPROFILE on -# Windows. +# shared ~/.databricks. setup_list_test() { sethome home - mkdir -p "${USERPROFILE:-$HOME}/.claude" } # use_compat_manifest copies the test's compat-manifest.json into the CLI diff --git a/acceptance/cmd/aitools/list/test.toml b/acceptance/cmd/aitools/list/test.toml index 58b4b06c245..7e9761d5c88 100644 --- a/acceptance/cmd/aitools/list/test.toml +++ b/acceptance/cmd/aitools/list/test.toml @@ -2,8 +2,9 @@ # api.github.com (latest skills release lookup). Env.DATABRICKS_SKILLS_BASE_URL = "$DATABRICKS_HOST" Env.DATABRICKS_CLI_GITHUB_API_URL = "$DATABRICKS_HOST" -# Read the compat manifest from the cache file that a test writes, even if the +# Use the skills ref and the compat manifest that each test sets, even if the # shell sets other values. +Env.DATABRICKS_SKILLS_REF = "" Env.DATABRICKS_FORCE_EMBEDDED_COMPAT = "false" Env.DATABRICKS_CACHE_ENABLED = "true" RecordRequests = true diff --git a/libs/aitools/installer/installer.go b/libs/aitools/installer/installer.go index 5d3489d742f..537d6c7322d 100644 --- a/libs/aitools/installer/installer.go +++ b/libs/aitools/installer/installer.go @@ -26,6 +26,7 @@ import ( "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/env" "github.com/databricks/cli/libs/log" + "github.com/databricks/cli/libs/versioncheck" "golang.org/x/mod/semver" "golang.org/x/sync/errgroup" ) @@ -140,24 +141,8 @@ func resolveLatestSkillsReleaseRef(ctx context.Context, allowFallback bool) (ref return "", false, fmt.Errorf("resolve latest skills release: %w", err) } -const ( - defaultGitHubAPIURL = "https://api.github.com" - - // gitHubAPIURLEnv overrides the GitHub API base URL, as in libs/versioncheck. - // Acceptance tests use it to point the latest-release lookup at the mock server. - gitHubAPIURLEnv = "DATABRICKS_CLI_GITHUB_API_URL" -) - -// getGitHubAPIURL returns the GitHub API base URL for the latest-release lookup. -func getGitHubAPIURL(ctx context.Context) string { - if base := env.Get(ctx, gitHubAPIURLEnv); base != "" { - return strings.TrimRight(base, "/") - } - return defaultGitHubAPIURL -} - func fetchLatestSkillsReleaseRef(ctx context.Context) (string, error) { - url := fmt.Sprintf("%s/repos/%s/%s/releases/latest", getGitHubAPIURL(ctx), skillsRepoOwner, skillsRepoName) + url := fmt.Sprintf("%s/repos/%s/%s/releases/latest", versioncheck.GitHubAPIURL(ctx), skillsRepoOwner, skillsRepoName) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return "", fmt.Errorf("failed to create request: %w", err) diff --git a/libs/versioncheck/versioncheck.go b/libs/versioncheck/versioncheck.go index 0a78cf35192..0b3bbd04957 100644 --- a/libs/versioncheck/versioncheck.go +++ b/libs/versioncheck/versioncheck.go @@ -27,9 +27,11 @@ const ( defaultGitHubAPIURL = "https://api.github.com" latestReleasePath = "/repos/databricks/cli/releases/latest" - // gitHubAPIURLEnv overrides the GitHub API base URL. It lets acceptance - // tests point the lookup at the mock server (and gives airgapped setups an - // escape hatch); it is not a documented, user-facing setting. + // gitHubAPIURLEnv overrides the GitHub API base URL for the CLI release + // lookup here and the skills release lookup in libs/aitools/installer. It + // lets acceptance tests point both lookups at the mock server (and gives + // airgapped setups an escape hatch); it is not a documented, user-facing + // setting. gitHubAPIURLEnv = "DATABRICKS_CLI_GITHUB_API_URL" // fetchTimeout bounds the release lookup. The explicit `version --check` @@ -127,13 +129,16 @@ type latestRelease struct { URL string `json:"url"` } -func fetchLatestRelease(ctx context.Context) (latestRelease, error) { - base := env.Get(ctx, gitHubAPIURLEnv) - if base == "" { - base = defaultGitHubAPIURL +// GitHubAPIURL returns the GitHub API base URL, with the gitHubAPIURLEnv override. +func GitHubAPIURL(ctx context.Context) string { + if base := env.Get(ctx, gitHubAPIURLEnv); base != "" { + return base } + return defaultGitHubAPIURL +} - url := base + latestReleasePath +func fetchLatestRelease(ctx context.Context) (latestRelease, error) { + url := GitHubAPIURL(ctx) + latestReleasePath req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return latestRelease{}, err diff --git a/libs/versioncheck/versioncheck_test.go b/libs/versioncheck/versioncheck_test.go index 84089e53171..a4e150a9e20 100644 --- a/libs/versioncheck/versioncheck_test.go +++ b/libs/versioncheck/versioncheck_test.go @@ -131,3 +131,11 @@ func TestCheck(t *testing.T) { assert.True(t, result.CheckFailed) }) } + +func TestGitHubAPIURL(t *testing.T) { + t.Setenv(gitHubAPIURLEnv, "") + assert.Equal(t, "https://api.github.com", GitHubAPIURL(t.Context())) + + t.Setenv(gitHubAPIURLEnv, "http://mirror.test") + assert.Equal(t, "http://mirror.test", GitHubAPIURL(t.Context())) +} From f0d37ff587345c3957e0c2b53b166f31f93537cf Mon Sep 17 00:00:00 2001 From: Grigory Panov Date: Thu, 22 Oct 2026 13:57:06 +0200 Subject: [PATCH 4/5] Trim the GitHub API URL and keep Windows coverage for aitools tests - GitHubAPIURL removes a trailing slash, and its doc comment names the env var; keep the original gitHubAPIURLEnv comment. - Keep acceptance/cmd/aitools out of test-exp-aitools sources, so a change there still runs task test on Windows. - Drop the text-output step in explicit-ref (agent config env vars can change it) and the setup_list_test wrapper, and fix comments. Co-authored-by: Isaac --- Taskfile.yml | 1 - .../cmd/aitools/list/explicit-ref/output.txt | 12 ------------ acceptance/cmd/aitools/list/explicit-ref/script | 4 ++-- acceptance/cmd/aitools/list/explicit-ref/test.toml | 3 ++- .../cmd/aitools/list/latest-lookup-fails/script | 7 ++++--- acceptance/cmd/aitools/list/latest/script | 3 ++- acceptance/cmd/aitools/list/pinned/script | 3 ++- acceptance/cmd/aitools/list/script.prepare | 9 +-------- libs/versioncheck/versioncheck.go | 14 +++++++------- libs/versioncheck/versioncheck_test.go | 3 +++ tools/testmask/targets_test.go | 7 ------- 11 files changed, 23 insertions(+), 43 deletions(-) diff --git a/Taskfile.yml b/Taskfile.yml index e93b0cbde0a..0c0eafffaa1 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -648,7 +648,6 @@ tasks: - libs/aitools/** - experimental/aitools/** - acceptance/apps/** - - acceptance/cmd/aitools/** - acceptance/experimental/aitools/** - "{{.EMBED_SOURCES}}" cmds: diff --git a/acceptance/cmd/aitools/list/explicit-ref/output.txt b/acceptance/cmd/aitools/list/explicit-ref/output.txt index 144d0f7b4f5..5f057827c14 100644 --- a/acceptance/cmd/aitools/list/explicit-ref/output.txt +++ b/acceptance/cmd/aitools/list/explicit-ref/output.txt @@ -1,12 +1,4 @@ ->>> [CLI] aitools list -Available raw skill directories (test-ref): - - NAME VERSION INSTALLED - test-skill v1.0.0 not installed - -0/1 raw skill directories installed (global) - >>> [CLI] aitools list --output json { "release": "test-ref", @@ -37,7 +29,3 @@ Available raw skill directories (test-ref): "method": "GET", "path": "/test-ref/manifest.json" } -{ - "method": "GET", - "path": "/test-ref/manifest.json" -} diff --git a/acceptance/cmd/aitools/list/explicit-ref/script b/acceptance/cmd/aitools/list/explicit-ref/script index f348eb7e97f..88653209b47 100644 --- a/acceptance/cmd/aitools/list/explicit-ref/script +++ b/acceptance/cmd/aitools/list/explicit-ref/script @@ -1,6 +1,6 @@ -setup_list_test +# Isolate HOME so parallel aitools tests don't race on a shared ~/.databricks. +sethome home -trace $CLI aitools list trace $CLI aitools list --output json | list_fields title "Only the skills manifest reaches the mock server: no latest-release lookup\n" diff --git a/acceptance/cmd/aitools/list/explicit-ref/test.toml b/acceptance/cmd/aitools/list/explicit-ref/test.toml index 9824d217206..16c46ded70d 100644 --- a/acceptance/cmd/aitools/list/explicit-ref/test.toml +++ b/acceptance/cmd/aitools/list/explicit-ref/test.toml @@ -1,2 +1,3 @@ -# DATABRICKS_SKILLS_REF skips the compat manifest and the latest-release lookup. +# DATABRICKS_SKILLS_REF sets the skills ref, so the CLI does not use the +# latest-release lookup. Env.DATABRICKS_SKILLS_REF = "test-ref" diff --git a/acceptance/cmd/aitools/list/latest-lookup-fails/script b/acceptance/cmd/aitools/list/latest-lookup-fails/script index 131f92568be..7d44f3d74c6 100644 --- a/acceptance/cmd/aitools/list/latest-lookup-fails/script +++ b/acceptance/cmd/aitools/list/latest-lookup-fails/script @@ -1,8 +1,9 @@ -setup_list_test +# Isolate HOME so parallel aitools tests don't race on a shared ~/.databricks. +sethome home use_compat_manifest -# list does not fall back to the embedded skills version, so a failed -# latest-release lookup fails the command. +# Known gap: list does not fall back to the embedded skills version, so a +# failed latest-release lookup fails the command. trace musterr $CLI aitools list --output json title "The lookup fails, so the manifest is never fetched\n" diff --git a/acceptance/cmd/aitools/list/latest/script b/acceptance/cmd/aitools/list/latest/script index c39f60a011a..e7a3f89da84 100644 --- a/acceptance/cmd/aitools/list/latest/script +++ b/acceptance/cmd/aitools/list/latest/script @@ -1,4 +1,5 @@ -setup_list_test +# Isolate HOME so parallel aitools tests don't race on a shared ~/.databricks. +sethome home use_compat_manifest trace $CLI aitools list --output json | list_fields diff --git a/acceptance/cmd/aitools/list/pinned/script b/acceptance/cmd/aitools/list/pinned/script index 62f0cd3a522..2125ca20877 100644 --- a/acceptance/cmd/aitools/list/pinned/script +++ b/acceptance/cmd/aitools/list/pinned/script @@ -1,4 +1,5 @@ -setup_list_test +# Isolate HOME so parallel aitools tests don't race on a shared ~/.databricks. +sethome home use_compat_manifest trace $CLI aitools list --output json | list_fields diff --git a/acceptance/cmd/aitools/list/script.prepare b/acceptance/cmd/aitools/list/script.prepare index 53efdf830f6..337b973834e 100644 --- a/acceptance/cmd/aitools/list/script.prepare +++ b/acceptance/cmd/aitools/list/script.prepare @@ -1,12 +1,5 @@ -# setup_list_test isolates HOME, so parallel aitools tests don't race on a -# shared ~/.databricks. -setup_list_test() { - sethome home -} - # use_compat_manifest copies the test's compat-manifest.json into the CLI -# cache. A fresh cache file replaces the remote compat manifest, and dev builds -# use its highest entry. +# cache. A fresh cache file replaces the remote compat manifest. use_compat_manifest() { mkdir -p "$DATABRICKS_CACHE_DIR" cp compat-manifest.json "$DATABRICKS_CACHE_DIR/compat-manifest.json" diff --git a/libs/versioncheck/versioncheck.go b/libs/versioncheck/versioncheck.go index 0b3bbd04957..29f34fedc61 100644 --- a/libs/versioncheck/versioncheck.go +++ b/libs/versioncheck/versioncheck.go @@ -27,11 +27,9 @@ const ( defaultGitHubAPIURL = "https://api.github.com" latestReleasePath = "/repos/databricks/cli/releases/latest" - // gitHubAPIURLEnv overrides the GitHub API base URL for the CLI release - // lookup here and the skills release lookup in libs/aitools/installer. It - // lets acceptance tests point both lookups at the mock server (and gives - // airgapped setups an escape hatch); it is not a documented, user-facing - // setting. + // gitHubAPIURLEnv overrides the GitHub API base URL. It lets acceptance + // tests point the lookup at the mock server (and gives airgapped setups an + // escape hatch); it is not a documented, user-facing setting. gitHubAPIURLEnv = "DATABRICKS_CLI_GITHUB_API_URL" // fetchTimeout bounds the release lookup. The explicit `version --check` @@ -129,10 +127,12 @@ type latestRelease struct { URL string `json:"url"` } -// GitHubAPIURL returns the GitHub API base URL, with the gitHubAPIURLEnv override. +// GitHubAPIURL returns the GitHub API base URL. DATABRICKS_CLI_GITHUB_API_URL +// overrides it for both the CLI release lookup here and the skills release +// lookup in libs/aitools/installer. func GitHubAPIURL(ctx context.Context) string { if base := env.Get(ctx, gitHubAPIURLEnv); base != "" { - return base + return strings.TrimRight(base, "/") } return defaultGitHubAPIURL } diff --git a/libs/versioncheck/versioncheck_test.go b/libs/versioncheck/versioncheck_test.go index a4e150a9e20..89de5ea42b3 100644 --- a/libs/versioncheck/versioncheck_test.go +++ b/libs/versioncheck/versioncheck_test.go @@ -138,4 +138,7 @@ func TestGitHubAPIURL(t *testing.T) { t.Setenv(gitHubAPIURLEnv, "http://mirror.test") assert.Equal(t, "http://mirror.test", GitHubAPIURL(t.Context())) + + t.Setenv(gitHubAPIURLEnv, "http://mirror.test/") + assert.Equal(t, "http://mirror.test", GitHubAPIURL(t.Context())) } diff --git a/tools/testmask/targets_test.go b/tools/testmask/targets_test.go index 1366c1cfbab..b2b058fc59d 100644 --- a/tools/testmask/targets_test.go +++ b/tools/testmask/targets_test.go @@ -38,13 +38,6 @@ func TestGetTargets(t *testing.T) { }, targets: []string{"test-exp-aitools"}, }, - { - name: "acceptance_cmd_aitools_triggers_aitools", - files: []string{ - "acceptance/cmd/aitools/list/latest/script", - }, - targets: []string{"test-exp-aitools"}, - }, { name: "sandbox", files: []string{ From a5040503f577cccad53337b3c3cd5254bbc1b9fd Mon Sep 17 00:00:00 2001 From: Grigory Panov Date: Thu, 22 Oct 2026 14:06:53 +0200 Subject: [PATCH 5/5] Make TestGitHubAPIURL table-driven and document the aitools sources Co-authored-by: Isaac --- Taskfile.yml | 2 ++ libs/versioncheck/versioncheck.go | 3 +-- libs/versioncheck/versioncheck_test.go | 24 ++++++++++++++++-------- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/Taskfile.yml b/Taskfile.yml index 0c0eafffaa1..f32f3ca3789 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -648,6 +648,8 @@ tasks: - libs/aitools/** - experimental/aitools/** - acceptance/apps/** + # acceptance/cmd/aitools is not listed on purpose: a change there runs + # `test`, which also runs on Windows. The -run pattern below still runs it. - acceptance/experimental/aitools/** - "{{.EMBED_SOURCES}}" cmds: diff --git a/libs/versioncheck/versioncheck.go b/libs/versioncheck/versioncheck.go index 29f34fedc61..409fa67ed00 100644 --- a/libs/versioncheck/versioncheck.go +++ b/libs/versioncheck/versioncheck.go @@ -128,8 +128,7 @@ type latestRelease struct { } // GitHubAPIURL returns the GitHub API base URL. DATABRICKS_CLI_GITHUB_API_URL -// overrides it for both the CLI release lookup here and the skills release -// lookup in libs/aitools/installer. +// overrides it. func GitHubAPIURL(ctx context.Context) string { if base := env.Get(ctx, gitHubAPIURLEnv); base != "" { return strings.TrimRight(base, "/") diff --git a/libs/versioncheck/versioncheck_test.go b/libs/versioncheck/versioncheck_test.go index 89de5ea42b3..0e4335a0017 100644 --- a/libs/versioncheck/versioncheck_test.go +++ b/libs/versioncheck/versioncheck_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/databricks/cli/internal/build" + "github.com/databricks/cli/libs/env" "github.com/stretchr/testify/assert" ) @@ -133,12 +134,19 @@ func TestCheck(t *testing.T) { } func TestGitHubAPIURL(t *testing.T) { - t.Setenv(gitHubAPIURLEnv, "") - assert.Equal(t, "https://api.github.com", GitHubAPIURL(t.Context())) - - t.Setenv(gitHubAPIURLEnv, "http://mirror.test") - assert.Equal(t, "http://mirror.test", GitHubAPIURL(t.Context())) - - t.Setenv(gitHubAPIURLEnv, "http://mirror.test/") - assert.Equal(t, "http://mirror.test", GitHubAPIURL(t.Context())) + tests := []struct { + name string + override string + want string + }{ + {"default", "", "https://api.github.com"}, + {"override", "http://mirror.test", "http://mirror.test"}, + {"override with trailing slash", "http://mirror.test/", "http://mirror.test"}, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := env.Set(t.Context(), gitHubAPIURLEnv, tc.override) + assert.Equal(t, tc.want, GitHubAPIURL(ctx)) + }) + } }