mindnet_obsidian/src/tests/interview/parseInterviewConfig.test.ts

349 lines
9.8 KiB
TypeScript
Raw Normal View History

import { describe, it, expect } from "vitest";
import { parseInterviewConfig, validateConfig } from "../../interview/parseInterviewConfig";
import type { InterviewConfig } from "../../interview/types";
describe("parseInterviewConfig", () => {
const fixtureYaml = `
version: "2.0"
frontmatterWhitelist:
- title
- id
- date
- tags
profiles:
- key: profile1
label: "Test Profile 1"
note_type: "note"
group: "test"
defaults:
template: "default"
steps:
- type: loop
key: items_loop
label: "Items Loop"
items:
- type: capture_frontmatter
key: title_field
label: "Title"
field: title
required: true
- type: capture_text
key: content_field
label: "Content"
required: false
- type: llm_dialog
key: llm_step
label: "LLM Dialog"
prompt: "What is the main topic?"
system_prompt: "You are a helpful assistant."
model: "gpt-4"
temperature: 0.7
max_tokens: 500
- key: profile2
label: "Test Profile 2"
note_type: "interview"
steps:
- type: capture_frontmatter
key: date_field
field: date
required: false
- type: llm_dialog
key: analysis
prompt: "Analyze the content"
`;
it("parses YAML correctly", () => {
const result = parseInterviewConfig(fixtureYaml);
expect(result.errors.length).toBe(0);
expect(result.config.version).toBe("2.0");
expect(result.config.frontmatterWhitelist).toEqual(["title", "id", "date", "tags"]);
expect(result.config.profiles.length).toBe(2);
});
it("parses profile structure correctly", () => {
const result = parseInterviewConfig(fixtureYaml);
const profile1 = result.config.profiles[0];
if (!profile1) throw new Error("Expected profile1");
expect(profile1.key).toBe("profile1");
expect(profile1.label).toBe("Test Profile 1");
expect(profile1.note_type).toBe("note");
expect(profile1.group).toBe("test");
expect(profile1.defaults).toEqual({ template: "default" });
expect(profile1.steps.length).toBe(2);
});
it("parses loop step correctly", () => {
const result = parseInterviewConfig(fixtureYaml);
const profile1 = result.config.profiles[0];
if (!profile1) throw new Error("Expected profile1");
const loopStep = profile1.steps[0];
if (!loopStep || loopStep.type !== "loop") {
throw new Error("Expected loop step");
}
expect(loopStep.key).toBe("items_loop");
expect(loopStep.label).toBe("Items Loop");
expect(loopStep.items.length).toBe(2);
const firstItem = loopStep.items[0];
if (!firstItem || firstItem.type !== "capture_frontmatter") {
throw new Error("Expected capture_frontmatter step");
}
expect(firstItem.key).toBe("title_field");
expect(firstItem.field).toBe("title");
expect(firstItem.required).toBe(true);
});
it("parses llm_dialog step correctly", () => {
const result = parseInterviewConfig(fixtureYaml);
const profile1 = result.config.profiles[0];
if (!profile1) throw new Error("Expected profile1");
const llmStep = profile1.steps[1];
if (!llmStep || llmStep.type !== "llm_dialog") {
throw new Error("Expected llm_dialog step");
}
expect(llmStep.key).toBe("llm_step");
expect(llmStep.prompt).toBe("What is the main topic?");
expect(llmStep.system_prompt).toBe("You are a helpful assistant.");
expect(llmStep.model).toBe("gpt-4");
expect(llmStep.temperature).toBe(0.7);
expect(llmStep.max_tokens).toBe(500);
});
it("parses capture_frontmatter step correctly", () => {
const result = parseInterviewConfig(fixtureYaml);
const profile2 = result.config.profiles[1];
if (!profile2) throw new Error("Expected profile2");
const captureStep = profile2.steps[0];
if (!captureStep || captureStep.type !== "capture_frontmatter") {
throw new Error("Expected capture_frontmatter step");
}
expect(captureStep.key).toBe("date_field");
expect(captureStep.field).toBe("date");
expect(captureStep.required).toBe(false);
});
it("parses capture_text step correctly", () => {
const result = parseInterviewConfig(fixtureYaml);
const profile1 = result.config.profiles[0];
if (!profile1) throw new Error("Expected profile1");
const loopStep = profile1.steps[0];
if (!loopStep || loopStep.type !== "loop") {
throw new Error("Expected loop step");
}
const captureTextStep = loopStep.items[1];
if (!captureTextStep || captureTextStep.type !== "capture_text") {
throw new Error("Expected capture_text step");
}
expect(captureTextStep.key).toBe("content_field");
expect(captureTextStep.required).toBe(false);
});
it("validates unique profile keys", () => {
const yamlWithDuplicate = `
version: "2.0"
frontmatterWhitelist: []
profiles:
- key: profile1
label: "Profile 1"
note_type: "note"
steps: []
- key: profile1
label: "Profile 1 Duplicate"
note_type: "note"
steps: []
`;
const result = parseInterviewConfig(yamlWithDuplicate);
const validationErrors = validateConfig(result.config);
expect(validationErrors.some(e => e.includes("Duplicate profile key"))).toBe(true);
});
it("validates capture_frontmatter.field in whitelist", () => {
const yamlWithInvalidField = `
version: "2.0"
frontmatterWhitelist:
- title
- id
profiles:
- key: profile1
label: "Profile 1"
note_type: "note"
steps:
- type: capture_frontmatter
key: invalid_field
field: invalid_field_name
`;
const result = parseInterviewConfig(yamlWithInvalidField);
const validationErrors = validateConfig(result.config);
expect(
validationErrors.some(e =>
e.includes("capture_frontmatter field 'invalid_field_name' not in frontmatterWhitelist")
)
).toBe(true);
});
it("validates capture_frontmatter.field in nested loop", () => {
const yamlWithNestedInvalidField = `
version: "2.0"
frontmatterWhitelist:
- title
profiles:
- key: profile1
label: "Profile 1"
note_type: "note"
steps:
- type: loop
key: items
items:
- type: capture_frontmatter
key: invalid
field: invalid_field
`;
const result = parseInterviewConfig(yamlWithNestedInvalidField);
const validationErrors = validateConfig(result.config);
expect(
validationErrors.some(e =>
e.includes("capture_frontmatter field 'invalid_field' not in frontmatterWhitelist")
)
).toBe(true);
});
it("handles invalid YAML gracefully", () => {
const invalidYaml = "invalid: yaml: content: [";
const result = parseInterviewConfig(invalidYaml);
expect(result.errors.length).toBeGreaterThan(0);
expect(result.config.profiles.length).toBe(0);
});
it("handles missing required fields", () => {
const incompleteYaml = `
version: "2.0"
frontmatterWhitelist: []
profiles:
- key: profile1
label: "Profile 1"
# missing note_type
steps: []
`;
const result = parseInterviewConfig(incompleteYaml);
// Profile should be skipped due to missing note_type
expect(result.config.profiles.length).toBe(0);
expect(result.errors.length).toBeGreaterThan(0);
});
it("parses YAML with 'kind' and 'id' fields (real-world format)", () => {
const yamlWithKindAndId = `
version: 2
frontmatter_whitelist:
- id
- title
- type
profiles:
- key: experience_hub
group: experience
label: "Experience – Hub"
note_type: experience
defaults:
status: active
steps:
- id: title
kind: capture_frontmatter
field: title
label: "Hub Titel"
required: true
- id: items
kind: loop
label: "Erlebnisse"
steps:
- id: item_text
kind: capture_text
label: "Erlebnis"
required: true
- id: review
kind: review
label: "Review & Apply"
`;
const result = parseInterviewConfig(yamlWithKindAndId);
// Log errors for debugging
if (result.errors.length > 0) {
console.log("Parse errors:", result.errors);
}
expect(result.errors.length).toBe(0);
expect(result.config.profiles.length).toBe(1);
const profile = result.config.profiles[0];
if (!profile) throw new Error("Expected profile");
expect(profile.key).toBe("experience_hub");
expect(profile.label).toBe("Experience – Hub");
expect(profile.note_type).toBe("experience");
expect(profile.group).toBe("experience");
expect(profile.steps.length).toBe(3);
// First step: capture_frontmatter
const titleStep = profile.steps[0];
if (!titleStep || titleStep.type !== "capture_frontmatter") {
throw new Error("Expected capture_frontmatter step");
}
expect(titleStep.key).toBe("title");
expect(titleStep.field).toBe("title");
expect(titleStep.label).toBe("Hub Titel");
expect(titleStep.required).toBe(true);
// Second step: loop
const loopStep = profile.steps[1];
if (!loopStep || loopStep.type !== "loop") {
throw new Error("Expected loop step");
}
expect(loopStep.key).toBe("items");
expect(loopStep.label).toBe("Erlebnisse");
expect(loopStep.items.length).toBe(1);
const nestedStep = loopStep.items[0];
if (!nestedStep || nestedStep.type !== "capture_text") {
throw new Error("Expected capture_text step in loop");
}
expect(nestedStep.key).toBe("item_text");
expect(nestedStep.label).toBe("Erlebnis");
expect(nestedStep.required).toBe(true);
// Third step: review
const reviewStep = profile.steps[2];
if (!reviewStep || reviewStep.type !== "review") {
throw new Error("Expected review step");
}
expect(reviewStep.key).toBe("review");
expect(reviewStep.label).toBe("Review & Apply");
});
});