Migrate all repos into monorepo context folders
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.
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
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
|
||||
*/
|
||||
});
|
||||
Reference in New Issue
Block a user