Bahn: aisupport, Analyse-O2C-C2S, awesome-bahn-mcp-servers, beam-mcp,
Confluence_Bot, db-planet-mcp-server, O2C-Harness, project-audit,
Projekt-KIQ-HP, teamlandkarte-mcp
Dhive: Jury-Voting
Privat: CV, NoteGraph (NOTE: NoteGraph needs complete redo after consolidation)
Shared: AI-Orchestrator, OrgMyLife, power_skills_and_more
Shared/references: symphony (read-only)
Bahn repos remain available as independent remotes - this monorepo
pulls them in via subtree, the originals are untouched.
130 lines
4.4 KiB
TypeScript
130 lines
4.4 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import fc from 'fast-check';
|
|
import { generateIntroduction } from './intro-generator';
|
|
import type { EventContext, IntroductionInput } from './intro-generator';
|
|
import type { Person, Skill, Experience, SkillCategory, SkillLevel } from '../schemas/types';
|
|
|
|
// --- Arbitraries ---
|
|
|
|
const kebabCaseId = fc
|
|
.array(
|
|
fc.stringMatching(/^[a-z0-9]{1,8}$/),
|
|
{ minLength: 1, maxLength: 4 }
|
|
)
|
|
.map(parts => parts.join('-'));
|
|
|
|
const isoDate = fc
|
|
.record({
|
|
year: fc.integer({ min: 1990, max: 2030 }),
|
|
month: fc.integer({ min: 1, max: 12 }),
|
|
})
|
|
.map(({ year, month }) => `${year}-${String(month).padStart(2, '0')}`);
|
|
|
|
const skillCategory: fc.Arbitrary<SkillCategory> = fc.constantFrom(
|
|
'programming-language',
|
|
'framework',
|
|
'methodology',
|
|
'soft-skill',
|
|
'domain'
|
|
);
|
|
|
|
const skillLevel: fc.Arbitrary<SkillLevel> = fc.constantFrom(
|
|
'beginner',
|
|
'intermediate',
|
|
'advanced',
|
|
'expert'
|
|
);
|
|
|
|
const eventContext: fc.Arbitrary<EventContext> = fc.constantFrom(
|
|
'technical',
|
|
'business',
|
|
'academic'
|
|
);
|
|
|
|
const skillArb: fc.Arbitrary<Skill> = fc.record({
|
|
id: kebabCaseId,
|
|
type: fc.constant('skill' as const),
|
|
created: isoDate,
|
|
modified: isoDate,
|
|
name: fc.string({ minLength: 2, maxLength: 30 }).filter(s => s.trim().length > 0),
|
|
category: skillCategory,
|
|
level: skillLevel,
|
|
years: fc.option(fc.integer({ min: 1, max: 20 }), { nil: undefined }),
|
|
}).map(fields => fields as Skill);
|
|
|
|
const experienceArb: fc.Arbitrary<Experience> = fc.record({
|
|
id: kebabCaseId,
|
|
type: fc.constant('experience' as const),
|
|
created: isoDate,
|
|
modified: isoDate,
|
|
title: fc.string({ minLength: 3, maxLength: 40 }).filter(s => s.trim().length > 0),
|
|
organization: fc.string({ minLength: 2, maxLength: 30 }).filter(s => s.trim().length > 0),
|
|
start: isoDate,
|
|
end: fc.option(isoDate, { nil: null }),
|
|
description: fc.option(fc.string({ minLength: 10, maxLength: 100 }), { nil: undefined }),
|
|
achievements: fc.option(
|
|
fc.array(fc.string({ minLength: 5, maxLength: 60 }).filter(s => s.trim().length > 0), { minLength: 1, maxLength: 3 }),
|
|
{ nil: undefined }
|
|
),
|
|
}).map(fields => fields as Experience);
|
|
|
|
const personArb: fc.Arbitrary<Person> = fc.record({
|
|
id: kebabCaseId,
|
|
type: fc.constant('person' as const),
|
|
created: isoDate,
|
|
modified: isoDate,
|
|
name: fc.record({
|
|
first: fc.string({ minLength: 2, maxLength: 15 }).filter(s => s.trim().length > 0),
|
|
last: fc.string({ minLength: 2, maxLength: 15 }).filter(s => s.trim().length > 0),
|
|
display: fc.string({ minLength: 4, maxLength: 30 }).filter(s => s.trim().length > 0),
|
|
}),
|
|
education: fc.option(
|
|
fc.array(fc.record({
|
|
institution: fc.string({ minLength: 3, maxLength: 30 }).filter(s => s.trim().length > 0),
|
|
degree: fc.string({ minLength: 2, maxLength: 20 }).filter(s => s.trim().length > 0),
|
|
field: fc.string({ minLength: 2, maxLength: 20 }).filter(s => s.trim().length > 0),
|
|
start: isoDate,
|
|
end: isoDate,
|
|
}), { minLength: 1, maxLength: 2 }),
|
|
{ nil: undefined }
|
|
),
|
|
}).map(fields => fields as Person);
|
|
|
|
const topicArb = fc.string({ minLength: 3, maxLength: 50 }).filter(s => s.trim().length > 0);
|
|
|
|
/** Generates a complete IntroductionInput with at least 1 skill and 1 experience */
|
|
const introductionInputArb: fc.Arbitrary<IntroductionInput> = fc.record({
|
|
person: personArb,
|
|
topic: topicArb,
|
|
eventContext: eventContext,
|
|
relevantSkills: fc.array(skillArb, { minLength: 1, maxLength: 5 }),
|
|
relevantExperiences: fc.array(experienceArb, { minLength: 1, maxLength: 4 }),
|
|
});
|
|
|
|
// --- Property 12: Introduction length variants (short < medium < long) ---
|
|
|
|
describe('Property 12: Introduction length variants', () => {
|
|
it('produces exactly three variants where short.length < medium.length < long.length', () => {
|
|
fc.assert(
|
|
fc.property(introductionInputArb, (input) => {
|
|
const result = generateIntroduction(input);
|
|
|
|
// Verify exactly three variants exist
|
|
expect(result).toHaveProperty('short');
|
|
expect(result).toHaveProperty('medium');
|
|
expect(result).toHaveProperty('long');
|
|
|
|
// Verify character count ordering: short < medium < long
|
|
expect(result.short.length).toBeGreaterThan(0);
|
|
expect(result.medium.length).toBeGreaterThan(result.short.length);
|
|
expect(result.long.length).toBeGreaterThan(result.medium.length);
|
|
}),
|
|
{ numRuns: 100 }
|
|
);
|
|
});
|
|
|
|
/**
|
|
* Validates: Requirements 6.2
|
|
*/
|
|
});
|