import { describe, expect, test } from 'bun:test' import { resolveInstallTargets } from '../../../src/agents/resolver' describe('resolveInstallTargets', () => { test('rejects dir and agent together before filesystem writes', async () => { await expect(resolveInstallTargets({ cwd: '/repo', dir: '/tmp/skills', agents: ['codex'], json: false, interactive: false })).rejects.toThrow('--dir cannot be used with --agent') }) test('falls back to cwd .agents skills when nothing detected', async () => { const targets = await resolveInstallTargets({ cwd: '/repo', agents: [], json: false, interactive: false, detected: [] }) expect(targets).toEqual([{ agent: 'generic', rootDir: '/repo/.agents/skills', scope: 'project', source: 'fallback' }]) }) test('uses explicit dir when provided', async () => { const targets = await resolveInstallTargets({ cwd: '/repo', dir: '/tmp/my-skills', json: false, interactive: false }) expect(targets).toEqual([{ agent: 'custom', rootDir: '/tmp/my-skills', scope: 'user', source: 'explicit' }]) }) test('explicit agent resolves the profile user root by default', async () => { const targets = await resolveInstallTargets({ cwd: '/repo', home: '/home/u', agents: ['codex'], json: false, interactive: false }) expect(targets).toEqual([{ agent: 'codex', rootDir: '/home/u/.codex/skills', scope: 'user', source: 'explicit' }]) }) test('explicit agent without scope labels root by userRoots membership when cwd === home', async () => { const targets = await resolveInstallTargets({ cwd: '/home/u', home: '/home/u', agents: ['codex'], json: false, interactive: false }) expect(targets[0]!.scope).toBe('user') expect(targets[0]!.rootDir).toBe('/home/u/.codex/skills') }) test('deduplicates repeated explicit agents by target root', async () => { const targets = await resolveInstallTargets({ cwd: '/repo', home: '/home/u', agents: ['codex', 'codex'], json: false, interactive: false }) expect(targets).toHaveLength(1) expect(targets[0]!.rootDir).toBe('/home/u/.codex/skills') }) test('returns single detected target directly', async () => { const targets = await resolveInstallTargets({ cwd: '/repo', agents: [], json: false, interactive: false, detected: [{ agent: 'codex', rootDir: '/repo/.codex/skills', scope: 'project', source: 'detected' }] }) expect(targets).toHaveLength(1) expect(targets[0]!.agent).toBe('codex') }) test('rejects multiple detected targets in non-interactive mode', async () => { await expect(resolveInstallTargets({ cwd: '/repo', agents: [], json: false, interactive: false, detected: [ { agent: 'codex', rootDir: '/repo/.codex/skills', scope: 'project', source: 'detected' }, { agent: 'claude-code', rootDir: '/repo/.claude/skills', scope: 'project', source: 'detected' } ] })).rejects.toThrow('multiple install targets detected') }) test('rejects unknown agent', async () => { await expect(resolveInstallTargets({ cwd: '/repo', agents: ['unknown-agent'], json: false, interactive: false })).rejects.toThrow('unknown agent: unknown-agent') }) test('rejects dir and scope together', async () => { await expect(resolveInstallTargets({ cwd: '/repo', dir: '/tmp/skills', scope: 'user', json: false, interactive: false })).rejects.toThrow('--dir cannot be used with --scope') }) test('scope=project + agent codex returns project root with project scope', async () => { const targets = await resolveInstallTargets({ cwd: '/repo', home: '/home/u', agents: ['codex'], scope: 'project', json: false, interactive: false }) expect(targets).toEqual([{ agent: 'codex', rootDir: '/repo/.codex/skills', scope: 'project', source: 'explicit' }]) }) test('scope=user + agent codex returns user root with user scope', async () => { const targets = await resolveInstallTargets({ cwd: '/repo', home: '/home/u', agents: ['codex'], scope: 'user', json: false, interactive: false }) expect(targets).toEqual([{ agent: 'codex', rootDir: '/home/u/.codex/skills', scope: 'user', source: 'explicit' }]) }) test('scope=user + cwd === home + agent codex still labels candidate as user', async () => { const targets = await resolveInstallTargets({ cwd: '/home/u', home: '/home/u', agents: ['codex'], scope: 'user', json: false, interactive: false }) expect(targets[0]!.scope).toBe('user') }) test('scope=user clean env falls back to user agents skills', async () => { const targets = await resolveInstallTargets({ cwd: '/repo', home: '/nonexistent-home-' + Math.random().toString(36).slice(2), agents: [], scope: 'user', json: false, interactive: false }) expect(targets).toEqual([{ agent: 'generic', rootDir: targets[0]!.rootDir, scope: 'user', source: 'fallback' }]) expect(targets[0]!.rootDir).toMatch(/\.agents\/skills$/) expect(targets[0]!.rootDir.startsWith('/nonexistent-home-')).toBe(true) }) test('scope=project clean env falls back to cwd agents skills', async () => { const targets = await resolveInstallTargets({ cwd: '/nonexistent-repo-' + Math.random().toString(36).slice(2), home: '/home/u', agents: [], scope: 'project', json: false, interactive: false }) expect(targets).toHaveLength(1) expect(targets[0]!.scope).toBe('project') expect(targets[0]!.source).toBe('fallback') expect(targets[0]!.rootDir).toMatch(/\.agents\/skills$/) }) test('scope filters detected candidates and falls back when filtered empty', async () => { const targets = await resolveInstallTargets({ cwd: '/repo', home: '/home/u', agents: [], scope: 'user', json: false, interactive: false, detected: [ { agent: 'codex', rootDir: '/repo/.codex/skills', scope: 'project', source: 'detected' } ] }) expect(targets).toHaveLength(1) expect(targets[0]!.source).toBe('fallback') expect(targets[0]!.scope).toBe('user') }) test('scope filters detected candidates keeps matching scope', async () => { const targets = await resolveInstallTargets({ cwd: '/repo', home: '/home/u', agents: [], scope: 'user', json: false, interactive: false, detected: [ { agent: 'codex', rootDir: '/repo/.codex/skills', scope: 'project', source: 'detected' }, { agent: 'codex', rootDir: '/home/u/.codex/skills', scope: 'user', source: 'detected' } ] }) expect(targets).toHaveLength(1) expect(targets[0]!.rootDir).toBe('/home/u/.codex/skills') expect(targets[0]!.scope).toBe('user') }) })