fix: preserve ClickClack setup secret refs

This commit is contained in:
Shakker
2026-07-15 13:05:10 +01:00
committed by Shakker
parent beece7f527
commit d1b839eaf6
2 changed files with 91 additions and 19 deletions
@@ -12,16 +12,20 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { useAutoCleanupTempDirTracker } from "../../../test/helpers/temp-dir.js";
const mocks = vi.hoisted(() => ({
createClient: vi.fn(),
me: vi.fn(),
resolveWorkspaceId: vi.fn(),
workspaces: vi.fn(),
}));
vi.mock("./http-client.js", () => ({
createClickClackClient: () => ({
me: mocks.me,
workspaces: mocks.workspaces,
}),
createClickClackClient: (options: unknown) => {
mocks.createClient(options);
return {
me: mocks.me,
workspaces: mocks.workspaces,
};
},
}));
vi.mock("./resolve.js", () => ({
@@ -54,6 +58,7 @@ function tokenCredential() {
describe("ClickClack setup wizard", () => {
beforeEach(() => {
mocks.createClient.mockReset();
mocks.me.mockReset();
mocks.resolveWorkspaceId.mockReset();
mocks.workspaces.mockReset();
@@ -126,6 +131,35 @@ describe("ClickClack setup wizard", () => {
hasConfiguredValue: true,
resolvedValue: "ccb_file",
});
const secretRefAccount = {
channels: {
clickclack: {
baseUrl: "https://clickclack.example",
token: { source: "file", provider: "vault", id: "/clickclack/token" },
workspace: "default",
},
},
} satisfies CoreConfig;
expect(await clickClackSetupWizard.status.resolveConfigured({ cfg: secretRefAccount })).toBe(
true,
);
expect(
credential.inspect({
cfg: secretRefAccount,
accountId: "default",
}),
).toMatchObject({
accountConfigured: true,
hasConfiguredValue: true,
resolvedValue: undefined,
});
expect(
clickClackSetupWizard.introNote?.shouldShow?.({
cfg: secretRefAccount,
accountId: "default",
}),
).toBe(false);
});
it("switches the default account to env auth before URL and workspace prompts", async () => {
@@ -192,6 +226,27 @@ describe("ClickClack setup wizard", () => {
);
});
it("uses the resolved setup credential for live validation", async () => {
await runSetupWizardFinalize({
finalize: clickClackSetupWizard.finalize,
cfg: {
channels: {
clickclack: {
baseUrl: "https://clickclack.example",
token: { source: "file", provider: "vault", id: "/clickclack/token" },
workspace: "default",
},
},
} satisfies CoreConfig,
credentialValues: { token: "ccb_resolved" },
});
expect(mocks.createClient).toHaveBeenCalledWith({
baseUrl: "https://clickclack.example",
token: "ccb_resolved",
});
});
it("keeps setup saved when the token is rejected", async () => {
mocks.me.mockRejectedValue({ status: 401 });
const note = vi.fn(async () => undefined);
+32 -15
View File
@@ -17,7 +17,7 @@ import {
applyClickClackSetupConfigPatch,
normalizeClickClackBaseUrl,
} from "./setup-core.js";
import type { CoreConfig } from "./types.js";
import type { CoreConfig, ResolvedClickClackAccount } from "./types.js";
const t = createSetupTranslator();
const channel = "clickclack" as const;
@@ -35,6 +35,20 @@ function isWorkspaceNotFound(error: unknown): boolean {
return error instanceof Error && error.message.startsWith("ClickClack workspace not found:");
}
function hasConfiguredClickClackCredential(account: ResolvedClickClackAccount): boolean {
return (
hasConfiguredSecretInput(account.config.token) || Boolean(account.config.tokenFile?.trim())
);
}
function isClickClackSetupConfigured(account: ResolvedClickClackAccount): boolean {
return Boolean(
account.baseUrl &&
account.workspace &&
(account.token || hasConfiguredClickClackCredential(account)),
);
}
export const clickClackSetupWizard: ChannelSetupWizard = {
channel,
status: createStandardChannelSetupStatus({
@@ -48,10 +62,12 @@ export const clickClackSetupWizard: ChannelSetupWizard = {
resolveConfigured: ({ cfg, accountId }) =>
(accountId ? [accountId] : listClickClackAccountIds(cfg as CoreConfig)).some(
(resolvedAccountId) =>
resolveClickClackAccount({
cfg: cfg as CoreConfig,
accountId: resolvedAccountId,
}).configured,
isClickClackSetupConfigured(
resolveClickClackAccount({
cfg: cfg as CoreConfig,
accountId: resolvedAccountId,
}),
),
),
}),
introNote: {
@@ -63,10 +79,12 @@ export const clickClackSetupWizard: ChannelSetupWizard = {
}),
],
shouldShow: ({ cfg, accountId }) =>
!resolveClickClackAccount({
cfg: cfg as CoreConfig,
accountId,
}).configured,
!isClickClackSetupConfigured(
resolveClickClackAccount({
cfg: cfg as CoreConfig,
accountId,
}),
),
},
credentials: [
{
@@ -83,11 +101,10 @@ export const clickClackSetupWizard: ChannelSetupWizard = {
cfg: cfg as CoreConfig,
accountId,
});
const hasConfiguredValue = hasConfiguredClickClackCredential(resolved);
return {
accountConfigured: resolved.configured,
hasConfiguredValue:
hasConfiguredSecretInput(resolved.config.token) ||
Boolean(resolved.config.tokenFile?.trim()),
accountConfigured: Boolean(resolved.token) || hasConfiguredValue,
hasConfiguredValue,
resolvedValue: resolved.token || undefined,
envValue:
accountId === DEFAULT_ACCOUNT_ID
@@ -148,7 +165,7 @@ export const clickClackSetupWizard: ChannelSetupWizard = {
}),
},
],
finalize: async ({ cfg, accountId, prompter }) => {
finalize: async ({ cfg, accountId, credentialValues, prompter }) => {
const account = resolveClickClackAccount({
cfg: cfg as CoreConfig,
accountId,
@@ -156,7 +173,7 @@ export const clickClackSetupWizard: ChannelSetupWizard = {
try {
const client = createClickClackClient({
baseUrl: account.baseUrl,
token: account.token,
token: credentialValues.token || account.token,
});
const me = await client.me();
const workspaceId = await resolveWorkspaceId(client, account.workspace);