skill-db.test.js 6.17 KB
/**
 * Unit Tests — skill-db.js
 * Tests core CRUD, search, favorites, version history
 */
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import fs from 'fs';

describe('skill-db', () => {
  let skillDb;
  const TEST_DB_PATH = '/tmp/test-skills-db.sqlite';
  const TEST_SKILL = {
    skillName: 'test_unit_skill',
    description: 'A test skill for unit testing',
    domain: 'testing',
    scope: 'unit',
    keywords: ['test', 'unit'],
    status: 'enable',
    version: '1.0.0',
    layer: 1,
    source: 'local',
    tags: ['test'],
    roles: ['developer'],
    scenes: ['testing'],
    platforms: ['general'],
  };

  beforeAll(() => {
    if (fs.existsSync(TEST_DB_PATH)) fs.unlinkSync(TEST_DB_PATH);
    process.env.SKILLS_DB_PATH = TEST_DB_PATH;
    delete require.cache[require.resolve('../../services/skill-db')];
    skillDb = require('../../services/skill-db');
  });

  afterAll(() => {
    try { fs.unlinkSync(TEST_DB_PATH); } catch {}
  });

  describe('registerSkill', () => {
    it('should register a new skill', () => {
      const skill = skillDb.registerSkill(TEST_SKILL);
      expect(skill).toBeDefined();
      expect(skill.skillName).toBe(TEST_SKILL.skillName);
      expect(skill.description).toBe(TEST_SKILL.description);
    });

    it('should throw on duplicate registration', () => {
      expect(() => skillDb.registerSkill(TEST_SKILL)).toThrow(/already exists/i);
    });
  });

  describe('getSkill', () => {
    it('should retrieve a registered skill by name', () => {
      const skill = skillDb.getSkill(TEST_SKILL.skillName);
      expect(skill).toBeDefined();
      expect(skill.skillName).toBe(TEST_SKILL.skillName);
      expect(skill.domain).toBe(TEST_SKILL.domain);
    });

    it('should return null for non-existent skill', () => {
      const skill = skillDb.getSkill('nonexistent_skill_xyz');
      expect(skill).toBeNull();
    });
  });

  describe('listSkills', () => {
    it('should list skills with pagination', () => {
      const skills = skillDb.listSkills({ limit: 10, offset: 0 });
      expect(Array.isArray(skills)).toBe(true);
      expect(skills.length).toBeGreaterThanOrEqual(1);
    });
  });

  describe('countSkills', () => {
    it('should return total count', () => {
      const count = skillDb.countSkills();
      expect(count).toBeGreaterThanOrEqual(1);
    });
  });

  describe('updateSkill', () => {
    it('should update skill metadata', () => {
      const updated = skillDb.updateSkill(TEST_SKILL.skillName, { description: 'Updated description' });
      expect(updated.description).toBe('Updated description');
    });
  });

  describe('searchSkills', () => {
    it('should return array from text search', () => {
      const results = skillDb.searchSkills('unit testing', { limit: 10 });
      expect(Array.isArray(results)).toBe(true);
    });

    it('should return empty for no match', () => {
      const results = skillDb.searchSkills('zzzznonexistent999', { limit: 10 });
      expect(results).toHaveLength(0);
    });
  });

  describe('bumpVersion', () => {
    it('should increment version', () => {
      const before = skillDb.getSkill(TEST_SKILL.skillName);
      const updated = skillDb.bumpVersion(TEST_SKILL.skillName, 'minor');
      expect(updated.version).not.toBe(before.version);
    });
  });

  describe('incrementUseCount', () => {
    it('should increase use count', () => {
      const before = skillDb.getSkill(TEST_SKILL.skillName);
      skillDb.incrementUseCount(TEST_SKILL.skillName);
      const after = skillDb.getSkill(TEST_SKILL.skillName);
      expect(after.useCount).toBe((before.useCount || 0) + 1);
    });
  });

  describe('incrementDownloadCount', () => {
    it('should increase download count', () => {
      const before = skillDb.getSkill(TEST_SKILL.skillName);
      skillDb.incrementDownloadCount(TEST_SKILL.skillName);
      const after = skillDb.getSkill(TEST_SKILL.skillName);
      expect(after.downloadCount).toBe((before.downloadCount || 0) + 1);
    });
  });

  describe('rowToSkill / skillToRow', () => {
    it('should round-trip correctly', () => {
      const skill = skillDb.getSkill(TEST_SKILL.skillName);
      const row = skillDb.skillToRow(skill);
      const back = skillDb.rowToSkill(row);
      expect(back.skillName).toBe(skill.skillName);
      expect(back.description).toBe(skill.description);
    });

    it('rowToSkill should handle null input', () => {
      expect(skillDb.rowToSkill(null)).toBeNull();
    });
  });

  describe('favorites', () => {
    const userId = 'test-user-123';

    it('should add a favorite', () => {
      const result = skillDb.addFavorite(TEST_SKILL.skillName, userId);
      expect(result).toBeDefined();
    });

    it('should check isFavorited', () => {
      const fav = skillDb.isFavorited(TEST_SKILL.skillName, userId);
      expect(fav).toBe(true);
    });

    it('should get favorite count', () => {
      const count = skillDb.getFavoriteCount(TEST_SKILL.skillName);
      expect(count).toBeGreaterThanOrEqual(1);
    });

    it('should remove a favorite', () => {
      skillDb.removeFavorite(TEST_SKILL.skillName, userId);
      const fav = skillDb.isFavorited(TEST_SKILL.skillName, userId);
      expect(fav).toBe(false);
    });
  });

  describe('recordDownload', () => {
    it('should record a download', () => {
      skillDb.recordDownload(TEST_SKILL.skillName, {
        userId: 'test-user', version: '1.0.0', ipAddress: '127.0.0.1',
      });
      const history = skillDb.getDownloadHistory(TEST_SKILL.skillName, { limit: 5 });
      expect(history.length).toBeGreaterThanOrEqual(1);
    });
  });

  describe('getStats', () => {
    it('should return database statistics', () => {
      const stats = skillDb.getStats();
      expect(stats).toBeDefined();
      expect(stats.total).toBeGreaterThanOrEqual(1);
      expect(stats.enabled).toBeDefined();
    });
  });

  describe('removeSkill', () => {
    it('should remove a skill', () => {
      const result = skillDb.removeSkill(TEST_SKILL.skillName);
      expect(result).toBe(true);
      expect(skillDb.getSkill(TEST_SKILL.skillName)).toBeNull();
    });

    it('should return false for non-existent skill', () => {
      const result = skillDb.removeSkill('nonexistent_xyz');
      expect(result).toBe(false);
    });
  });
});