From 9adc49212aeb64449352f8ae3f41c65353af87d5 Mon Sep 17 00:00:00 2001 From: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com> Date: Tue, 1 Sep 2026 16:11:18 +0800 Subject: [PATCH] test(cli): cover upgrade selection and fallback boundaries Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com> --- cli/test/integration/upgrade-command.test.ts | 74 ++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/cli/test/integration/upgrade-command.test.ts b/cli/test/integration/upgrade-command.test.ts index 5dc628e0..8694c3a0 100644 --- a/cli/test/integration/upgrade-command.test.ts +++ b/cli/test/integration/upgrade-command.test.ts @@ -187,6 +187,47 @@ describe('upgrade command', () => { expect(noNamespaceMatch.stderr).toContain('not installed') }) + test('target filters select deterministically and missing matches never install', async () => { + const env = await createTempHome() + const skill = { namespace: 'global', slug: 'filtered', version: '1.0.0', fingerprint: 'fp', zipBytes: makeSkillZip('# v1') } + const registry = await startFakeRegistry({ skills: [skill] }) + registries.push(registry) + const rootDir = join(env.cwd, 'skills') + await mkdir(rootDir, { recursive: true }) + await runCli(['install', '@global/filtered', '--dir', rootDir, '--registry', registry.url], { + HOME: env.home, + USERPROFILE: env.home + }) + + for (const args of [ + ['--dir', rootDir], + ['--agent', 'custom'], + ['--namespace', 'global'], + ['--registry', registry.url] + ]) { + const result = await runCli(['upgrade', 'filtered', ...args, '--force', '--check'], { + HOME: env.home, + USERPROFILE: env.home + }) + expect(result.exitCode).toBe(0) + } + + for (const args of [ + ['--dir', join(env.cwd, 'missing')], + ['--agent', 'codex'], + ['--namespace', 'missing'], + ['--registry', 'http://unmatched.invalid'] + ]) { + const result = await runCli(['upgrade', 'filtered', ...args, '--check'], { + HOME: env.home, + USERPROFILE: env.home + }) + expect(result.exitCode).toBe(5) + expect(result.stderr).toContain('not installed') + } + expect(registry.received.downloads).toBe(1) + }) + test('one resolved archive is reused for every managed target', async () => { const env = await createTempHome() const skill = { @@ -266,6 +307,39 @@ describe('upgrade command', () => { expect(registry.received.downloads).toBe(1) }) + test('keeps local files when resolve is unavailable or same-version content drifts', async () => { + const env = await createTempHome() + const failures: { resolve?: 'server_error' } = {} + const skill = { namespace: 'global', slug: 'resilient', version: '1.0.0', fingerprint: 'fp-v1', zipBytes: makeSkillZip('# v1') } + const registry = await startFakeRegistry({ skills: [skill], failures }) + registries.push(registry) + const rootDir = join(env.cwd, 'skills') + await mkdir(rootDir, { recursive: true }) + await runCli(['install', '@global/resilient', '--dir', rootDir, '--registry', registry.url], { + HOME: env.home, + USERPROFILE: env.home + }) + + failures.resolve = 'server_error' + const unavailable = await runCli([ + 'upgrade', '@global/resilient', '--registry', registry.url, '--force', '--json' + ], { HOME: env.home, USERPROFILE: env.home }) + expect(unavailable.exitCode).toBe(6) + expect(JSON.parse(unavailable.stdout).items[0].action).toBe('blocked') + expect(await readFile(join(rootDir, 'resilient', 'SKILL.md'), 'utf-8')).toBe('# v1') + + delete failures.resolve + skill.fingerprint = 'fp-drift' + skill.zipBytes = makeSkillZip('# changed without version bump') + const drifted = await runCli([ + 'upgrade', '@global/resilient', '--registry', registry.url, '--force', '--json' + ], { HOME: env.home, USERPROFILE: env.home }) + expect(drifted.exitCode).toBe(6) + expect(JSON.parse(drifted.stdout).items[0].reason).toContain('without a newer version') + expect(await readFile(join(rootDir, 'resilient', 'SKILL.md'), 'utf-8')).toBe('# v1') + expect(registry.received.downloads).toBe(1) + }) + test('a blocked batch reports a plan and does not claim successful writes', async () => { const env = await createTempHome() const first = { namespace: 'global', slug: 'first', version: '1.0.0', fingerprint: 'first-v1', zipBytes: makeSkillZip('# first v1') }