All files / server/services/tutor/curriculum content-repository.ts

90.9% Statements 20/22
83.33% Branches 15/18
100% Functions 4/4
100% Lines 15/15

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43                                9x 9x 9x       73x 73x 70x       82x 80x       82x 436791x 2x 2x 2x 2x   80x    
import { parse as parseYaml } from "yaml";
import {
  curriculumManifestSchema,
  curriculumTopicSchema,
  validateCurriculumManifest,
  validateCurriculumTopic,
  type CurriculumManifest,
  type CurriculumTopic,
} from "./curriculum-schema";
 
/**
 * YAML parsing for curriculum manifests and topics with the same safety rules
 * as Course Content (no YAML tags, unique keys). Production loads Course
 * Content through CourseContentLoader; tests and fixtures use these helpers.
 */
export function parseManifest(source: string): CurriculumManifest {
  const parsed = curriculumManifestSchema.safeParse(parseSafeYaml(source));
  Iif (!parsed.success) throw new Error("Invalid curriculum manifest");
  return validateCurriculumManifest(parsed.data);
}
 
export function parseTopic(source: string): CurriculumTopic {
  const parsed = curriculumTopicSchema.safeParse(parseSafeYaml(source));
  if (!parsed.success) throw new Error("Invalid curriculum topic");
  return validateCurriculumTopic(parsed.data);
}
 
function parseSafeYaml(source: string): unknown {
  if (containsYamlTag(source)) throw new Error("YAML tags are not allowed");
  return parseYaml(source, { schema: "core", uniqueKeys: true });
}
 
function containsYamlTag(source: string): boolean {
  for (let index = 0; index < source.length; index += 1) {
    if (source[index] !== "!") continue;
    const previous = source[index - 1];
    Iif (previous !== undefined && !/\s|,|:|\{|\}|\[|\]/.test(previous)) continue;
    const next = source[index + 1];
    Eif (next === "!" || next === "<" || (next !== undefined && /[A-Za-z]/.test(next))) return true;
  }
  return false;
}