/** * @license * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import * as fs from 'node:fs/promises'; import * as path from 'node:path'; import * as os from 'node:os'; import { installSkill, linkSkill } from './skillUtils.js'; describe('skillUtils', () => { let tempDir: string; const projectRoot = path.resolve(__dirname, '../../../../../'); beforeEach(async () => { tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'skill-utils-test-')); vi.spyOn(process, 'cwd').mockReturnValue(tempDir); }); afterEach(async () => { await fs.rm(tempDir, { recursive: true, force: true }); vi.restoreAllMocks(); }); const itif = (condition: boolean) => (condition ? it : it.skip); describe('linkSkill', () => { // TODO: issue 19388 - Enable linkSkill tests on Windows itif(process.platform !== 'win32')( 'should successfully link from a local directory', async () => { // Create a mock skill directory const mockSkillSourceDir = path.join(tempDir, 'mock-skill-source'); const skillSubDir = path.join(mockSkillSourceDir, 'test-skill'); await fs.mkdir(skillSubDir, { recursive: true }); await fs.writeFile( path.join(skillSubDir, 'SKILL.md'), '---\nname: test-skill\ndescription: test\n---\nbody', ); const skills = await linkSkill( mockSkillSourceDir, 'workspace', () => {}, ); expect(skills.length).toBe(1); expect(skills[0].name).toBe('test-skill'); const linkedPath = path.join(tempDir, '.gemini/skills', 'test-skill'); const stats = await fs.lstat(linkedPath); expect(stats.isSymbolicLink()).toBe(true); const linkTarget = await fs.readlink(linkedPath); expect(path.resolve(linkTarget)).toBe(path.resolve(skillSubDir)); }, ); itif(process.platform !== 'win32')( 'should overwrite existing skill at destination', async () => { const mockSkillSourceDir = path.join(tempDir, 'mock-skill-source'); const skillSubDir = path.join(mockSkillSourceDir, 'test-skill'); await fs.mkdir(skillSubDir, { recursive: true }); await fs.writeFile( path.join(skillSubDir, 'SKILL.md'), '---\nname: test-skill\ndescription: test\n---\nbody', ); const targetDir = path.join(tempDir, '.gemini/skills'); await fs.mkdir(targetDir, { recursive: true }); const existingPath = path.join(targetDir, 'test-skill'); await fs.mkdir(existingPath); const skills = await linkSkill( mockSkillSourceDir, 'workspace', () => {}, ); expect(skills.length).toBe(1); const stats = await fs.lstat(existingPath); expect(stats.isSymbolicLink()).toBe(true); }, ); it('should abort linking if consent is rejected', async () => { const mockSkillSourceDir = path.join(tempDir, 'mock-skill-source'); const skillSubDir = path.join(mockSkillSourceDir, 'test-skill'); await fs.mkdir(skillSubDir, { recursive: true }); await fs.writeFile( path.join(skillSubDir, 'SKILL.md'), '---\nname: test-skill\ndescription: test\n---\nbody', ); const requestConsent = vi.fn().mockResolvedValue(false); await expect( linkSkill(mockSkillSourceDir, 'workspace', () => {}, requestConsent), ).rejects.toThrow('Skill linking cancelled by user.'); expect(requestConsent).toHaveBeenCalled(); // Verify it was NOT linked const linkedPath = path.join(tempDir, '.gemini/skills', 'test-skill'); const exists = await fs.lstat(linkedPath).catch(() => null); expect(exists).toBeNull(); }); it('should throw error if multiple skills with same name are discovered', async () => { const mockSkillSourceDir = path.join(tempDir, 'mock-skill-source'); const skillDir1 = path.join(mockSkillSourceDir, 'skill1'); const skillDir2 = path.join(mockSkillSourceDir, 'skill2'); await fs.mkdir(skillDir1, { recursive: true }); await fs.mkdir(skillDir2, { recursive: true }); await fs.writeFile( path.join(skillDir1, 'SKILL.md'), '---\nname: duplicate-skill\ndescription: desc1\n---\nbody1', ); await fs.writeFile( path.join(skillDir2, 'SKILL.md'), '---\nname: duplicate-skill\ndescription: desc2\n---\nbody2', ); await expect( linkSkill(mockSkillSourceDir, 'workspace', () => {}), ).rejects.toThrow('Duplicate skill name "duplicate-skill" found'); }); }); it('should successfully install from a .skill file', async () => { const skillPath = path.join(projectRoot, 'weather-skill.skill'); // Ensure the file exists const exists = await fs.stat(skillPath).catch(() => null); if (!exists) { // If we can't find it in CI or other environments, we skip or use a mock. // For now, since it exists in the user's environment, this test will pass there. return; } const skills = await installSkill( skillPath, 'workspace', undefined, async () => {}, ); expect(skills.length).toBeGreaterThan(0); expect(skills[0].name).toBe('weather-skill'); // Verify it was copied to the workspace skills dir const installedPath = path.join(tempDir, '.gemini/skills', 'weather-skill'); const installedExists = await fs.stat(installedPath).catch(() => null); expect(installedExists?.isDirectory()).toBe(true); const skillMdExists = await fs .stat(path.join(installedPath, 'SKILL.md')) .catch(() => null); expect(skillMdExists?.isFile()).toBe(true); }); it('should successfully install from a local directory', async () => { // Create a mock skill directory const mockSkillDir = path.join(tempDir, 'mock-skill-source'); const skillSubDir = path.join(mockSkillDir, 'test-skill'); await fs.mkdir(skillSubDir, { recursive: true }); await fs.writeFile( path.join(skillSubDir, 'SKILL.md'), '---\nname: test-skill\ndescription: test\n---\nbody', ); const skills = await installSkill( mockSkillDir, 'workspace', undefined, async () => {}, ); expect(skills.length).toBe(1); expect(skills[0].name).toBe('test-skill'); const installedPath = path.join(tempDir, '.gemini/skills', 'test-skill'); const installedExists = await fs.stat(installedPath).catch(() => null); expect(installedExists?.isDirectory()).toBe(true); }); it('should abort installation if consent is rejected', async () => { const mockSkillDir = path.join(tempDir, 'mock-skill-source'); const skillSubDir = path.join(mockSkillDir, 'test-skill'); await fs.mkdir(skillSubDir, { recursive: true }); await fs.writeFile( path.join(skillSubDir, 'SKILL.md'), '---\nname: test-skill\ndescription: test\n---\nbody', ); const requestConsent = vi.fn().mockResolvedValue(false); await expect( installSkill( mockSkillDir, 'workspace', undefined, async () => {}, requestConsent, ), ).rejects.toThrow('Skill installation cancelled by user.'); expect(requestConsent).toHaveBeenCalled(); // Verify it was NOT copied const installedPath = path.join(tempDir, '.gemini/skills', 'test-skill'); const installedExists = await fs.stat(installedPath).catch(() => null); expect(installedExists).toBeNull(); }); });