Skip to main content
Glama
tag-matching-integration.test.ts7.69 kB
// test/tag-matching-integration.test.ts import { describe, it, before, after } from 'node:test'; import assert from 'node:assert/strict'; import { mkdtemp, rm, writeFile, mkdir } from 'fs/promises'; import { join } from 'path'; import { tmpdir } from 'os'; import { MarkdownManager } from '../src/services/markdown-manager.js'; import type { TimeEntry, CompanyConfig } from '../src/types/index.js'; /** * Integration test for case-insensitive tag matching * * Tests the complete flow: * 1. Write entries with mixed-case tags * 2. Read them back from file * 3. Verify tags are normalized for matching * 4. Verify original capitalization is preserved in file */ describe('Tag Matching Integration', () => { let testDir: string; let originalEnv: string | undefined; before(async () => { // Create temporary directory for test files testDir = await mkdtemp(join(tmpdir(), 'time-tracking-test-')); // Set environment to use test directory originalEnv = process.env.TIME_TRACKING_DIR; process.env.TIME_TRACKING_DIR = testDir; // Create test company config const config: CompanyConfig = { company: 'TestCompany', commitments: { total: { limit: 40, unit: 'hours/week' }, development: { limit: 30, unit: 'hours/week' }, meeting: { limit: 10, unit: 'hours/week' } }, projects: { 'Frontend': { tags: ['frontend', 'ui', 'react'], commitment: 'development' } }, tagMappings: { 'dev': 'development', 'sync': 'meeting' } }; await writeFile( join(testDir, 'config.json'), JSON.stringify(config, null, 2) ); }); after(async () => { // Restore environment if (originalEnv !== undefined) { process.env.TIME_TRACKING_DIR = originalEnv; } else { delete process.env.TIME_TRACKING_DIR; } // Clean up test directory await rm(testDir, { recursive: true, force: true }); }); it('should preserve capitalization in file but match case-insensitively', async () => { const manager = new MarkdownManager(); // Use a unique week to avoid test interference const entry1: TimeEntry = { date: '2025-01-06', // Week 2 time: '14:30', task: 'Code review', duration: 2, tags: ['Dev'] // Mixed case, maps to development }; const entry2: TimeEntry = { date: '2025-01-06', // Week 2 time: '16:00', task: 'Meeting', duration: 1, tags: ['SYNC'] // Maps to meeting }; const entry3: TimeEntry = { date: '2025-01-06', // Week 2 time: '17:00', task: 'More dev', duration: 1.5, tags: ['dev'] // Different case, should map to development }; await manager.addEntry('default', entry1); await manager.addEntry('default', entry2); await manager.addEntry('default', entry3); // Read back the summary from Week 2 const summary = await manager.getWeeklySummary('default', 2025, 2); assert.ok(summary, 'Summary should exist'); // Verify commitments are aggregated correctly (case-insensitive) // Both 'Dev' (2h) and 'dev' (1.5h) should map to 'development' assert.equal(summary.byCommitment['development'], 3.5, 'Both Dev and dev should contribute to development commitment'); // 'SYNC' should map to 'meeting' assert.equal(summary.byCommitment['meeting'], 1, 'SYNC should map to meeting commitment'); // Verify tags are normalized in summary // Tags should be lowercase and aggregated assert.equal(summary.byTag['dev'], 3.5, 'Dev and dev should aggregate under lowercase "dev"'); assert.equal(summary.byTag['sync'], 1, 'SYNC should be normalized to lowercase "sync"'); }); it('should handle tag mapping with mixed case in config keys', async () => { const manager = new MarkdownManager(); // Create config with mixed-case mapping keys const configPath = join(testDir, 'test-mixed-case', 'config.json'); await mkdir(join(testDir, 'test-mixed-case'), { recursive: true }); const config: CompanyConfig = { company: 'MixedCase', commitments: { total: { limit: 40, unit: 'hours/week' }, development: { limit: 30, unit: 'hours/week' } }, tagMappings: { 'Dev': 'development', // Mixed case in config 'SYNC': 'meeting' // Upper case in config } }; await writeFile(configPath, JSON.stringify(config, null, 2)); // Add entry with lowercase tags, use Week 3 const entry: TimeEntry = { date: '2025-01-13', // Week 3 time: '14:30', task: 'Development work', duration: 2, tags: ['dev', 'sync'] // Lowercase tags }; // Set COMPANIES env var for multi-company mode const prevCompanies = process.env.COMPANIES; process.env.COMPANIES = 'test-mixed-case'; try { await manager.addEntry('test-mixed-case', entry); const summary = await manager.getWeeklySummary('test-mixed-case', 2025, 3); assert.ok(summary, 'Summary should exist'); // Tags should still map correctly despite case mismatch assert.equal(summary.byCommitment['development'], 2, 'Lowercase "dev" should map to development via mixed-case config key "Dev"'); } finally { // Restore env if (prevCompanies !== undefined) { process.env.COMPANIES = prevCompanies; } else { delete process.env.COMPANIES; } } }); it('should handle duplicate tags with different capitalization', async () => { const manager = new MarkdownManager(); // User might accidentally provide duplicate tags with different cases // Use Week 4 to avoid interference const entry: TimeEntry = { date: '2025-01-20', // Week 4 time: '14:30', task: 'Task with duplicate tags', duration: 2, tags: ['dev', 'Dev', 'DEV', 'test'] // Duplicates with different cases }; await manager.addEntry('default', entry); const summary = await manager.getWeeklySummary('default', 2025, 4); assert.ok(summary, 'Summary should exist'); // Fixed: Entry should only count once per commitment, regardless of duplicate tags // Even though entry has ['dev', 'Dev', 'DEV'] (3 tags), all map to 'development' // So it should count as 2h once, not 6h (3 × 2h) assert.equal(summary.byCommitment['development'], 2, 'Entry counts once per commitment even with duplicate tags mapping to same commitment'); // Tags are still tracked separately in byTag (for tag statistics) // All three 'dev' variants aggregate under lowercase 'dev' assert.equal(summary.byTag['dev'], 6, 'All dev variants aggregate under lowercase "dev" in tag statistics (3 instances × 2h)'); assert.equal(summary.byTag['test'], 2, 'Non-duplicate tag counts once'); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/markwharton/time-tracking-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server