@conform-ed/contracts 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +2 -0
- package/.turbo/turbo-format.log +6 -0
- package/.turbo/turbo-lint.log +4 -0
- package/.turbo/turbo-test.log +196 -0
- package/.turbo/turbo-typecheck.log +2 -0
- package/README.md +52 -0
- package/caliper-v1_2-zod-templates.md +123 -0
- package/case-v1_1-zod-templates.md +290 -0
- package/cat-v1_0-zod-templates.md +73 -0
- package/cc-v1_3-zod-templates.md +531 -0
- package/cc-v1_4-zod-templates.md +247 -0
- package/clr-v2_0-zod-templates.md +117 -0
- package/cmi5-v1_0-zod-templates.md +9 -0
- package/lti-zod-templates.md +11 -0
- package/oneroster-v1_2-zod-templates.md +76 -0
- package/open-badges-v3_0-zod-templates.md +66 -0
- package/package.json +42 -0
- package/qti-v2_1-zod-templates.md +45 -0
- package/qti-v2_2-zod-templates.md +32 -0
- package/qti-v3_0_1-zod-templates.md +421 -0
- package/src/adapter.ts +83 -0
- package/src/caliper/v1_2/caliper_v1p2_bootcamp_schema.ts +781 -0
- package/src/caliper/v1_2/index.ts +37 -0
- package/src/caliper/v1_2/shared.ts +122 -0
- package/src/caliper/v1_2/textual_requirements.ts +219 -0
- package/src/case/v1_1/case_v1p1_cfassociation_jsonschema1.ts +1 -0
- package/src/case/v1_1/case_v1p1_cfassociationgrouping_jsonschema1.ts +1 -0
- package/src/case/v1_1/case_v1p1_cfassociationset_jsonschema1.ts +1 -0
- package/src/case/v1_1/case_v1p1_cfconceptset_jsonschema1.ts +1 -0
- package/src/case/v1_1/case_v1p1_cfdocument_jsonschema1.ts +1 -0
- package/src/case/v1_1/case_v1p1_cfdocumentset_jsonschema1.ts +1 -0
- package/src/case/v1_1/case_v1p1_cfitem_jsonschema1.ts +1 -0
- package/src/case/v1_1/case_v1p1_cfitemtypeset_jsonschema1.ts +1 -0
- package/src/case/v1_1/case_v1p1_cflicense_jsonschema1.ts +1 -0
- package/src/case/v1_1/case_v1p1_cfpackage_jsonschema1.ts +1 -0
- package/src/case/v1_1/case_v1p1_cfrubric_jsonschema1.ts +1 -0
- package/src/case/v1_1/case_v1p1_cfsubjectset_jsonschema1.ts +1 -0
- package/src/case/v1_1/case_v1p1_imsx_statusinfo_jsonschema1.ts +1 -0
- package/src/case/v1_1/case_v1p1_openapi3_restbinding_schema.ts +113 -0
- package/src/case/v1_1/index.ts +95 -0
- package/src/case/v1_1/shared.ts +384 -0
- package/src/cat/v1_0/cat_v1p0_restbinding_operations_schema.ts +75 -0
- package/src/cat/v1_0/index.ts +124 -0
- package/src/cat/v1_0/shared.ts +345 -0
- package/src/clr/v2_0/clr_v2p0_achievementcredential_schema.ts +16 -0
- package/src/clr/v2_0/clr_v2p0_clrcredential_schema.ts +9 -0
- package/src/clr/v2_0/clr_v2p0_endorsementcredential_schema.ts +11 -0
- package/src/clr/v2_0/clr_v2p0_getclrcredentialsresponse_schema.ts +1 -0
- package/src/clr/v2_0/clr_v2p0_imsx_statusinfo_schema.ts +8 -0
- package/src/clr/v2_0/clr_v2p0_profile_schema.ts +9 -0
- package/src/clr/v2_0/index.ts +22 -0
- package/src/clr/v2_0/shared.ts +206 -0
- package/src/cmi5/index.ts +3 -0
- package/src/cmi5/v1_0/index.ts +134 -0
- package/src/common-cartridge/v1_3/ccv1p3_imsccauth_v1p3.ts +26 -0
- package/src/common-cartridge/v1_3/ccv1p3_imscp_v1p2_v1p0.ts +352 -0
- package/src/common-cartridge/v1_3/ccv1p3_imscsmd_v1p0.ts +35 -0
- package/src/common-cartridge/v1_3/ccv1p3_imsdt_v1p3.ts +33 -0
- package/src/common-cartridge/v1_3/ccv1p3_imswl_v1p3.ts +23 -0
- package/src/common-cartridge/v1_3/ccv1p3_lomccltilink_v1p0.ts +14 -0
- package/src/common-cartridge/v1_3/ccv1p3_lommanifest_v1p0.ts +14 -0
- package/src/common-cartridge/v1_3/ccv1p3_lomresource_v1p0.ts +14 -0
- package/src/common-cartridge/v1_3/ccv1p3_qtiasiv1p2p1_v1p0.ts +964 -0
- package/src/common-cartridge/v1_3/index.ts +41 -0
- package/src/common-cartridge/v1_3/lom-internal.ts +396 -0
- package/src/common-cartridge/v1_3/shared.ts +68 -0
- package/src/common-cartridge/v1_4/core/ccv1p4_imscp_v1p2_v1p0.ts +360 -0
- package/src/common-cartridge/v1_4/core/ccv1p4_lommanifest_v1p0.ts +14 -0
- package/src/common-cartridge/v1_4/core/ccv1p4_lomresource_v1p0.ts +14 -0
- package/src/common-cartridge/v1_4/extension/cc_extresource_assignmentv1p0_v1p0.ts +61 -0
- package/src/common-cartridge/v1_4/extension/ccv1p4_cpextensionv1p2_v1p0.ts +20 -0
- package/src/common-cartridge/v1_4/extension/ims_openvideov1p0_v1p0.ts +325 -0
- package/src/common-cartridge/v1_4/index.ts +104 -0
- package/src/common-cartridge/v1_4/k12/ccv1p4_imscp_v1p2_v1p0.ts +19 -0
- package/src/common-cartridge/v1_4/k12/ccv1p4_imscp_v1p2_v1p0_thin.ts +17 -0
- package/src/common-cartridge/v1_4/k12/ccv1p4_lommanifest_v1p0.ts +14 -0
- package/src/common-cartridge/v1_4/k12/ccv1p4_lomresource_v1p0.ts +14 -0
- package/src/common-cartridge/v1_4/lom-internal.ts +476 -0
- package/src/common-cartridge/v1_4/shared/ccv1p4_imsccauth_v1p4.ts +26 -0
- package/src/common-cartridge/v1_4/shared/ccv1p4_imscsmd_v1p1.ts +36 -0
- package/src/common-cartridge/v1_4/shared/ccv1p4_imsdt_v1p4.ts +33 -0
- package/src/common-cartridge/v1_4/shared/ccv1p4_imslticc_v1p4.ts +45 -0
- package/src/common-cartridge/v1_4/shared/ccv1p4_imswl_v1p4.ts +23 -0
- package/src/common-cartridge/v1_4/shared/ccv1p4_lomccltilink_v1p0.ts +14 -0
- package/src/common-cartridge/v1_4/shared/ccv1p4_qtiasiv1p2p1_v1p0.ts +964 -0
- package/src/common-cartridge/v1_4/shared/imsbasiclti_v1p0p1.ts +24 -0
- package/src/common-cartridge/v1_4/shared/imslticm_v1p0.ts +23 -0
- package/src/common-cartridge/v1_4/shared/imslticp_v1p0.ts +57 -0
- package/src/common-cartridge/v1_4/shared/lineitem_v1p0.ts +35 -0
- package/src/common-cartridge/v1_4/shared/resourcea11ymetadata-20210915.ts +110 -0
- package/src/common-cartridge/v1_4/shared.ts +68 -0
- package/src/common-cartridge/v1_4/thin/ccv1p4_imscp_v1p2_v1p0.ts +243 -0
- package/src/common-cartridge/v1_4/thin/ccv1p4_lommanifest_v1p0.ts +14 -0
- package/src/common-cartridge/v1_4/thin/ccv1p4_lomresource_v1p0.ts +14 -0
- package/src/common-cartridge/v1_4/vdex/imsmd_loose_v1p3p2.ts +13 -0
- package/src/common-cartridge/v1_4/vdex/imsvdex_v1p0.ts +124 -0
- package/src/config.ts +121 -0
- package/src/index.ts +32 -0
- package/src/lti/ags/v2_0/index.ts +97 -0
- package/src/lti/deep-linking/v2_0/index.ts +100 -0
- package/src/lti/index.ts +26 -0
- package/src/lti/nrps/v2_0/index.ts +69 -0
- package/src/lti/proctoring/v1_0/index.ts +131 -0
- package/src/lti/shared.ts +66 -0
- package/src/lti/v1_3/index.ts +84 -0
- package/src/oneroster/v1_2/index.ts +156 -0
- package/src/oneroster/v1_2/or_v1p2_csv_binding_schema.ts +427 -0
- package/src/oneroster/v1_2/or_v1p2_gradebook_restbinding_schema.ts +120 -0
- package/src/oneroster/v1_2/or_v1p2_gradebook_service_schema.ts +243 -0
- package/src/oneroster/v1_2/or_v1p2_resource_restbinding_schema.ts +24 -0
- package/src/oneroster/v1_2/or_v1p2_resource_service_schema.ts +47 -0
- package/src/oneroster/v1_2/or_v1p2_rostering_restbinding_schema.ts +84 -0
- package/src/oneroster/v1_2/or_v1p2_rostering_service_schema.ts +288 -0
- package/src/oneroster/v1_2/shared.ts +90 -0
- package/src/open-badges/v3_0/index.ts +20 -0
- package/src/open-badges/v3_0/ob_v3p0_achievementcredential_schema.ts +17 -0
- package/src/open-badges/v3_0/ob_v3p0_endorsementcredential_schema.ts +11 -0
- package/src/open-badges/v3_0/ob_v3p0_getopenbadgecredentialsresponse_schema.ts +1 -0
- package/src/open-badges/v3_0/ob_v3p0_imsx_statusinfo_schema.ts +8 -0
- package/src/open-badges/v3_0/ob_v3p0_profile_schema.ts +9 -0
- package/src/open-badges/v3_0/shared.ts +458 -0
- package/src/qti/v2-internal.ts +1683 -0
- package/src/qti/v2_1/apipv1p0_qtiextv2p1_v1p0.ts +3 -0
- package/src/qti/v2_1/imsqti_metadata_v2p1.ts +3 -0
- package/src/qti/v2_1/imsqti_result_v2p1.ts +3 -0
- package/src/qti/v2_1/imsqti_usagedata_v2p1.ts +3 -0
- package/src/qti/v2_1/imsqti_v2p1p2.ts +71 -0
- package/src/qti/v2_1/index.ts +53 -0
- package/src/qti/v2_1/qtiv2p1_imscpv1p2_v1p0.ts +3 -0
- package/src/qti/v2_1/schemas.ts +15 -0
- package/src/qti/v2_1/shared.ts +3 -0
- package/src/qti/v2_2/apipv1p0_qtiextv2p2_v1p0p1.ts +3 -0
- package/src/qti/v2_2/imsqti_metadata_v2p2.ts +3 -0
- package/src/qti/v2_2/imsqti_result_v2p2.ts +3 -0
- package/src/qti/v2_2/imsqti_usagedata_v2p2.ts +3 -0
- package/src/qti/v2_2/imsqti_v2p2p4.ts +76 -0
- package/src/qti/v2_2/imsqtiv2p2p4_html5_v1p0.ts +8 -0
- package/src/qti/v2_2/index.ts +59 -0
- package/src/qti/v2_2/qtiv2p2_csm_v2p2.ts +4 -0
- package/src/qti/v2_2/qtiv2p2_imscpv1p2_v1p0.ts +3 -0
- package/src/qti/v2_2/schemas.ts +19 -0
- package/src/qti/v2_2/shared.ts +3 -0
- package/src/qti/v3_0_1/assessment-internal.ts +1509 -0
- package/src/qti/v3_0_1/imsqti_asiv3p0p1_v1p0.ts +57 -0
- package/src/qti/v3_0_1/imsqti_itemv3p0p1_v1p0.ts +60 -0
- package/src/qti/v3_0_1/imsqti_metadatav3p0_v1p0.ts +73 -0
- package/src/qti/v3_0_1/imsqti_outcomev3p0p1_v1p0.ts +11 -0
- package/src/qti/v3_0_1/imsqti_responseprocessingv3p0p1_v1p0.ts +17 -0
- package/src/qti/v3_0_1/imsqti_resultv3p0_v1p0.ts +222 -0
- package/src/qti/v3_0_1/imsqti_sectionv3p0p1_v1p0.ts +15 -0
- package/src/qti/v3_0_1/imsqti_stimulusv3p0p1_v1p0.ts +15 -0
- package/src/qti/v3_0_1/imsqti_testv3p0p1_v1p0.ts +49 -0
- package/src/qti/v3_0_1/imsqti_usagedatav3p0_v1p0.ts +77 -0
- package/src/qti/v3_0_1/imsqtiv3p0_afa3p0pnp_v1p0.ts +269 -0
- package/src/qti/v3_0_1/index.ts +50 -0
- package/src/qti/v3_0_1/processing-internal.ts +667 -0
- package/src/qti/v3_0_1/shared.ts +146 -0
- package/src/qti/v3_0_1/variables-internal.ts +274 -0
- package/src/shared/imsx-status.ts +49 -0
- package/src/summary.ts +70 -0
- package/src/vc-data-model/v2_0/index.ts +27 -0
- package/src/vc-data-model/v2_0/shared.ts +206 -0
- package/src/xapi/index.ts +12 -0
- package/src/xapi/shared.ts +444 -0
- package/src/xapi/v1_0_3/index.ts +169 -0
- package/src/xapi/v2_0/index.ts +256 -0
- package/test/caliper-v1_2.test.ts +270 -0
- package/test/case-v1_1.test.ts +147 -0
- package/test/cat-v1_0.test.ts +338 -0
- package/test/cc-v1_4.test.ts +239 -0
- package/test/clr-v2_0.test.ts +225 -0
- package/test/cmi5-v1_0.test.ts +196 -0
- package/test/contracts.test.ts +125 -0
- package/test/fixtures/cmi5/extended-cmi5.xml +72 -0
- package/test/fixtures/cmi5/simple-cmi5.xml +26 -0
- package/test/lti.test.ts +146 -0
- package/test/oneroster-v1_2.test.ts +234 -0
- package/test/open-badges-v3_0.test.ts +144 -0
- package/test/qti-v2_1.test.ts +146 -0
- package/test/qti-v2_2.test.ts +154 -0
- package/test/qti-v3_0_1.test.ts +576 -0
- package/test/schema-examples.test.ts +101 -0
- package/test/vc-data-model-v2_0.test.ts +63 -0
- package/test/xapi.test.ts +384 -0
- package/tsconfig.json +7 -0
- package/vc-data-model-v2_0-zod-templates.md +43 -0
- package/xapi-zod-templates.md +95 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { expect, test, describe } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { CaseV1_1, Case11DerivedZodTemplates } from "@conform-ed/contracts/case/v1_1";
|
|
4
|
+
|
|
5
|
+
describe("CASE v1.1 Schemas", () => {
|
|
6
|
+
describe("Core entity schemas", () => {
|
|
7
|
+
test("should validate a valid CFAssociation", () => {
|
|
8
|
+
const association = {
|
|
9
|
+
identifier: "3d8235b4-7f2c-4b8d-9e1a-2c5f3b6d7a8e",
|
|
10
|
+
associationType: "isChildOf",
|
|
11
|
+
uri: "http://example.org/cf/associations/1",
|
|
12
|
+
originNodeURI: {
|
|
13
|
+
title: "Origin",
|
|
14
|
+
identifier: "4e9346c5-8e3d-5c9e-0f2b-3d6e4c7e8b9f",
|
|
15
|
+
uri: "http://example.org/origin",
|
|
16
|
+
},
|
|
17
|
+
destinationNodeURI: {
|
|
18
|
+
title: "Destination",
|
|
19
|
+
identifier: "5f0457d6-9f4e-6d0f-1f3c-4e7f5d8f9c0f",
|
|
20
|
+
uri: "http://example.org/destination",
|
|
21
|
+
},
|
|
22
|
+
lastChangeDateTime: "2024-01-15T10:30:00Z",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const result = CaseV1_1.Schemas.CFAssociation.safeParse(association);
|
|
26
|
+
expect(result.success).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("should reject CFAssociation with invalid UUID", () => {
|
|
30
|
+
const association = {
|
|
31
|
+
identifier: "invalid-uuid",
|
|
32
|
+
associationType: "isChildOf",
|
|
33
|
+
uri: "http://example.org/cf/associations/1",
|
|
34
|
+
originNodeURI: {
|
|
35
|
+
title: "Origin",
|
|
36
|
+
identifier: "invalid",
|
|
37
|
+
uri: "http://example.org/origin",
|
|
38
|
+
},
|
|
39
|
+
destinationNodeURI: {
|
|
40
|
+
title: "Destination",
|
|
41
|
+
identifier: "invalid",
|
|
42
|
+
uri: "http://example.org/destination",
|
|
43
|
+
},
|
|
44
|
+
lastChangeDateTime: "2024-01-15T10:30:00Z",
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const result = CaseV1_1.Schemas.CFAssociation.safeParse(association);
|
|
48
|
+
expect(result.success).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe("REST binding operations", () => {
|
|
53
|
+
test("should expose getCFAssociation operation", () => {
|
|
54
|
+
const op = CaseV1_1.RestBinding.Operations.getCFAssociation;
|
|
55
|
+
expect(op.method).toBe("GET");
|
|
56
|
+
expect(op.path).toContain("/cfAssociations/");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("should expose listCFAssociations operation", () => {
|
|
60
|
+
const op = CaseV1_1.RestBinding.Operations.listCFAssociations;
|
|
61
|
+
expect(op.method).toBe("GET");
|
|
62
|
+
expect(op.path).toBe("/cfAssociations");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("should expose getCFPackage operation", () => {
|
|
66
|
+
const op = CaseV1_1.RestBinding.Operations.getCFPackage;
|
|
67
|
+
expect(op.method).toBe("GET");
|
|
68
|
+
expect(op.path).toContain("/cfPackages/");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("should expose listCFItems operation", () => {
|
|
72
|
+
const op = CaseV1_1.RestBinding.Operations.listCFItems;
|
|
73
|
+
expect(op.method).toBe("GET");
|
|
74
|
+
expect(op.path).toBe("/cfItems");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("should expose getCFRubric operation", () => {
|
|
78
|
+
const op = CaseV1_1.RestBinding.Operations.getCFRubric;
|
|
79
|
+
expect(op.method).toBe("GET");
|
|
80
|
+
expect(op.path).toContain("/cfRubrics/");
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe("Shared validators", () => {
|
|
85
|
+
test("should validate RFC4122 UUID", () => {
|
|
86
|
+
const uuid = "3d8235b4-7f2c-4b8d-9e1a-2c5f3b6d7a8e";
|
|
87
|
+
const result = CaseV1_1.Shared.Uuid.safeParse(uuid);
|
|
88
|
+
expect(result.success).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("should reject invalid UUID format", () => {
|
|
92
|
+
const uuid = "not-a-uuid";
|
|
93
|
+
const result = CaseV1_1.Shared.Uuid.safeParse(uuid);
|
|
94
|
+
expect(result.success).toBe(false);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("should validate ISO8601 datetime", () => {
|
|
98
|
+
const dt = "2024-01-15T10:30:00Z";
|
|
99
|
+
const result = CaseV1_1.Shared.DateTime.safeParse(dt);
|
|
100
|
+
expect(result.success).toBe(true);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("should validate link URI", () => {
|
|
104
|
+
const uri = {
|
|
105
|
+
title: "Item Resource",
|
|
106
|
+
identifier: "3d8235b4-7f2c-4b8d-9e1a-2c5f3b6d7a8e",
|
|
107
|
+
uri: "http://example.org/cf/items/1",
|
|
108
|
+
};
|
|
109
|
+
const result = CaseV1_1.Shared.LinkUri.safeParse(uri);
|
|
110
|
+
expect(result.success).toBe(true);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe("Extensible vocabularies", () => {
|
|
115
|
+
test("should allow standard association types", () => {
|
|
116
|
+
const vocab = CaseV1_1.Shared.ExtensionEnum(["isChildOf", "isPartOf", "relatedTo"]);
|
|
117
|
+
expect(vocab.safeParse("isChildOf").success).toBe(true);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("should allow ext:* custom extensions", () => {
|
|
121
|
+
const vocab = CaseV1_1.Shared.ExtensionEnum(["isChildOf"]);
|
|
122
|
+
const customExt = "ext:custom-type";
|
|
123
|
+
expect(vocab.safeParse(customExt).success).toBe(true);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("should reject invalid ext: format", () => {
|
|
127
|
+
const vocab = CaseV1_1.Shared.ExtensionEnum(["isChildOf"]);
|
|
128
|
+
const invalidExt = "ext:";
|
|
129
|
+
expect(vocab.safeParse(invalidExt).success).toBe(false);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe("Derived templates", () => {
|
|
134
|
+
test("should expose spec links", () => {
|
|
135
|
+
const { specLinks } = Case11DerivedZodTemplates;
|
|
136
|
+
expect(specLinks.base).toContain("imsglobal.org/spec/case/v1p1");
|
|
137
|
+
expect(specLinks.jsonSchema).toContain("purl.imsglobal.org");
|
|
138
|
+
expect(specLinks.openApi).toContain("purl.imsglobal.org");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("should document scope coverage", () => {
|
|
142
|
+
const { scopes } = Case11DerivedZodTemplates;
|
|
143
|
+
expect(scopes.jsonSchema).toContain("CFAssociation");
|
|
144
|
+
expect(scopes.restBinding).toBeDefined();
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
});
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { describe, it, expect } from "bun:test";
|
|
2
|
+
import { CatV1_0 } from "../src/cat/v1_0/index";
|
|
3
|
+
|
|
4
|
+
describe("CAT v1.0 Zod Schemas", () => {
|
|
5
|
+
// Shared validator tests
|
|
6
|
+
describe("Shared validators", () => {
|
|
7
|
+
it("UuidSchema validates RFC4122 compliant UUIDs", () => {
|
|
8
|
+
const uuid = CatV1_0.Shared.Uuid;
|
|
9
|
+
expect(uuid.parse("550e8400-e29b-41d4-a716-446655440000")).toBeDefined();
|
|
10
|
+
expect(() => uuid.parse("not-a-uuid")).toThrow();
|
|
11
|
+
expect(() => uuid.parse("550e8400-e29b-41d4-7716-446655440000")).toThrow(); // Invalid variant
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("DateTimeSchema validates ISO8601 datetimes", () => {
|
|
15
|
+
const dt = CatV1_0.Shared.DateTime;
|
|
16
|
+
expect(dt.parse("2026-05-28T15:07:29Z")).toBeDefined();
|
|
17
|
+
expect(dt.parse("2026-05-28T15:07:29.742+08:00")).toBeDefined();
|
|
18
|
+
expect(() => dt.parse("2026-05-28")).toThrow();
|
|
19
|
+
expect(() => dt.parse("not-a-date")).toThrow();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("ExtensionEnum supports standard values and ext:* vendor extensions", () => {
|
|
23
|
+
const enumSchema = CatV1_0.Shared.ExtensionEnum(["standard", "value"]);
|
|
24
|
+
expect(enumSchema.parse("standard")).toBe("standard");
|
|
25
|
+
expect(enumSchema.parse("ext:custom-vendor-extension")).toBe("ext:custom-vendor-extension");
|
|
26
|
+
expect(() => enumSchema.parse("unknown-value")).toThrow();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("OutcomeVariableType supports outcome types and vendor extensions", () => {
|
|
30
|
+
expect(CatV1_0.Shared.OutcomeVariableType.parse("float")).toBe("float");
|
|
31
|
+
expect(CatV1_0.Shared.OutcomeVariableType.parse("string")).toBe("string");
|
|
32
|
+
expect(CatV1_0.Shared.OutcomeVariableType.parse("ext:custom-type")).toBe("ext:custom-type");
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Data type tests
|
|
37
|
+
describe("Core data types", () => {
|
|
38
|
+
it("OutcomeVariable validates outcome variable structure", () => {
|
|
39
|
+
const outcomeVar = {
|
|
40
|
+
identifier: "SCORE",
|
|
41
|
+
cardinality: "single",
|
|
42
|
+
baseType: "float",
|
|
43
|
+
value: 85.5,
|
|
44
|
+
};
|
|
45
|
+
expect(CatV1_0.Schemas.OutcomeVariable.parse(outcomeVar)).toBeDefined();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("OutcomeVariable rejects additional properties", () => {
|
|
49
|
+
const outcomeVar = {
|
|
50
|
+
identifier: "SCORE",
|
|
51
|
+
cardinality: "single",
|
|
52
|
+
baseType: "float",
|
|
53
|
+
value: 85.5,
|
|
54
|
+
extraField: "should-fail",
|
|
55
|
+
};
|
|
56
|
+
expect(() => CatV1_0.Schemas.OutcomeVariable.parse(outcomeVar)).toThrow();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("ResponseVariable validates candidate responses", () => {
|
|
60
|
+
const response = {
|
|
61
|
+
identifier: "RESPONSE_1",
|
|
62
|
+
cardinality: "single",
|
|
63
|
+
baseType: "identifier",
|
|
64
|
+
value: "choice_a",
|
|
65
|
+
};
|
|
66
|
+
expect(CatV1_0.Schemas.ResponseVariable.parse(response)).toBeDefined();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("ItemRef validates item references", () => {
|
|
70
|
+
const itemRef = {
|
|
71
|
+
identifier: "item_1",
|
|
72
|
+
href: "http://example.com/item/item_1.xml",
|
|
73
|
+
};
|
|
74
|
+
expect(CatV1_0.Schemas.ItemRef.parse(itemRef)).toBeDefined();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("ItemPool validates item pool with multiple items", () => {
|
|
78
|
+
const itemPool = {
|
|
79
|
+
itemRefs: [
|
|
80
|
+
{ identifier: "item_1", href: "http://example.com/item/item_1.xml" },
|
|
81
|
+
{ identifier: "item_2", href: "http://example.com/item/item_2.xml" },
|
|
82
|
+
{ identifier: "item_3", href: "http://example.com/item/item_3.xml" },
|
|
83
|
+
],
|
|
84
|
+
};
|
|
85
|
+
expect(CatV1_0.Schemas.ItemPool.parse(itemPool)).toBeDefined();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("SectionData validates complete section configuration", () => {
|
|
89
|
+
const sectionData = {
|
|
90
|
+
sectionIdentifier: "adaptive_section_1",
|
|
91
|
+
assessmentSectionId: "550e8400-e29b-41d4-a716-446655440000",
|
|
92
|
+
sectionHref: "http://example.com/section/adaptive_section_1.xml",
|
|
93
|
+
itemPool: {
|
|
94
|
+
itemRefs: [
|
|
95
|
+
{ identifier: "item_1", href: "http://example.com/item/item_1.xml" },
|
|
96
|
+
{ identifier: "item_2", href: "http://example.com/item/item_2.xml" },
|
|
97
|
+
],
|
|
98
|
+
},
|
|
99
|
+
createdAt: "2026-05-28T15:07:29Z",
|
|
100
|
+
};
|
|
101
|
+
expect(CatV1_0.Schemas.SectionData.parse(sectionData)).toBeDefined();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("ItemStage validates items to present to candidate", () => {
|
|
105
|
+
const itemStage = {
|
|
106
|
+
stageId: "550e8400-e29b-41d4-a716-446655440000",
|
|
107
|
+
items: [
|
|
108
|
+
{ identifier: "item_1", href: "http://example.com/item/item_1.xml" },
|
|
109
|
+
{ identifier: "item_2", href: "http://example.com/item/item_2.xml" },
|
|
110
|
+
],
|
|
111
|
+
assessmentSectionId: "550e8400-e29b-41d4-a716-446655440001",
|
|
112
|
+
};
|
|
113
|
+
expect(CatV1_0.Schemas.ItemStage.parse(itemStage)).toBeDefined();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("AssessmentResult validates candidate responses and outcomes", () => {
|
|
117
|
+
const result = {
|
|
118
|
+
resultId: "550e8400-e29b-41d4-a716-446655440000",
|
|
119
|
+
assessmentSectionId: "550e8400-e29b-41d4-a716-446655440001",
|
|
120
|
+
itemsAttempted: [
|
|
121
|
+
{
|
|
122
|
+
itemRef: {
|
|
123
|
+
identifier: "item_1",
|
|
124
|
+
href: "http://example.com/item/item_1.xml",
|
|
125
|
+
},
|
|
126
|
+
attemptNumber: 1,
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
responseVariables: [
|
|
130
|
+
{
|
|
131
|
+
identifier: "RESPONSE_1",
|
|
132
|
+
cardinality: "single",
|
|
133
|
+
baseType: "identifier",
|
|
134
|
+
value: "choice_a",
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
outcomeVariables: [
|
|
138
|
+
{
|
|
139
|
+
identifier: "SCORE",
|
|
140
|
+
cardinality: "single",
|
|
141
|
+
baseType: "float",
|
|
142
|
+
value: 1.0,
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
continuationRequired: true,
|
|
146
|
+
submittedAt: "2026-05-28T15:07:29Z",
|
|
147
|
+
};
|
|
148
|
+
expect(CatV1_0.Schemas.AssessmentResult.parse(result)).toBeDefined();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("CatEngineResultReport validates CAT engine response", () => {
|
|
152
|
+
const report = {
|
|
153
|
+
reportId: "550e8400-e29b-41d4-a716-446655440000",
|
|
154
|
+
assessmentSectionId: "550e8400-e29b-41d4-a716-446655440001",
|
|
155
|
+
estimatedAbility: 0.45,
|
|
156
|
+
abilityStandardError: 0.32,
|
|
157
|
+
sectionScore: 72.5,
|
|
158
|
+
recommendation: "continue",
|
|
159
|
+
generatedAt: "2026-05-28T15:07:29Z",
|
|
160
|
+
};
|
|
161
|
+
expect(CatV1_0.Schemas.CatEngineResultReport.parse(report)).toBeDefined();
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("SessionInfo validates session state", () => {
|
|
165
|
+
const session = {
|
|
166
|
+
sessionId: "550e8400-e29b-41d4-a716-446655440000",
|
|
167
|
+
assessmentSectionId: "550e8400-e29b-41d4-a716-446655440001",
|
|
168
|
+
candidateId: "candidate_123",
|
|
169
|
+
status: "active",
|
|
170
|
+
itemsCompleted: 3,
|
|
171
|
+
startedAt: "2026-05-28T15:00:00Z",
|
|
172
|
+
};
|
|
173
|
+
expect(CatV1_0.Schemas.SessionInfo.parse(session)).toBeDefined();
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Request/response validation tests
|
|
178
|
+
describe("REST operation payloads", () => {
|
|
179
|
+
it("CreateSectionRequest validates section creation", () => {
|
|
180
|
+
const request = {
|
|
181
|
+
sectionData: {
|
|
182
|
+
sectionIdentifier: "adaptive_section_1",
|
|
183
|
+
assessmentSectionId: "550e8400-e29b-41d4-a716-446655440000",
|
|
184
|
+
sectionHref: "http://example.com/section/adaptive_section_1.xml",
|
|
185
|
+
itemPool: {
|
|
186
|
+
itemRefs: [{ identifier: "item_1", href: "http://example.com/item/item_1.xml" }],
|
|
187
|
+
},
|
|
188
|
+
createdAt: "2026-05-28T15:07:29Z",
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
expect(CatV1_0.Schemas.CreateSectionRequest.parse(request)).toBeDefined();
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("CreateSectionResponse validates section creation response", () => {
|
|
195
|
+
const response = {
|
|
196
|
+
sectionId: "550e8400-e29b-41d4-a716-446655440000",
|
|
197
|
+
status: "created",
|
|
198
|
+
};
|
|
199
|
+
expect(CatV1_0.Schemas.CreateSectionResponse.parse(response)).toBeDefined();
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("CreateSessionRequest validates session creation", () => {
|
|
203
|
+
const request = {
|
|
204
|
+
assessmentSectionId: "550e8400-e29b-41d4-a716-446655440000",
|
|
205
|
+
candidateId: "candidate_123",
|
|
206
|
+
};
|
|
207
|
+
expect(CatV1_0.Schemas.CreateSessionRequest.parse(request)).toBeDefined();
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("CreateSessionResponse validates session creation response", () => {
|
|
211
|
+
const response = {
|
|
212
|
+
sessionId: "550e8400-e29b-41d4-a716-446655440000",
|
|
213
|
+
status: "active",
|
|
214
|
+
};
|
|
215
|
+
expect(CatV1_0.Schemas.CreateSessionResponse.parse(response)).toBeDefined();
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("SubmitResultsRequest validates result submission", () => {
|
|
219
|
+
const request = {
|
|
220
|
+
sessionId: "550e8400-e29b-41d4-a716-446655440000",
|
|
221
|
+
result: {
|
|
222
|
+
resultId: "550e8400-e29b-41d4-a716-446655440001",
|
|
223
|
+
assessmentSectionId: "550e8400-e29b-41d4-a716-446655440002",
|
|
224
|
+
itemsAttempted: [
|
|
225
|
+
{
|
|
226
|
+
itemRef: {
|
|
227
|
+
identifier: "item_1",
|
|
228
|
+
href: "http://example.com/item/item_1.xml",
|
|
229
|
+
},
|
|
230
|
+
attemptNumber: 1,
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
continuationRequired: true,
|
|
234
|
+
submittedAt: "2026-05-28T15:07:29Z",
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
expect(CatV1_0.Schemas.SubmitResultsRequest.parse(request)).toBeDefined();
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it("SubmitResultsResponse validates CAT engine result report", () => {
|
|
241
|
+
const response = {
|
|
242
|
+
reportId: "550e8400-e29b-41d4-a716-446655440000",
|
|
243
|
+
assessmentSectionId: "550e8400-e29b-41d4-a716-446655440001",
|
|
244
|
+
recommendation: "finish",
|
|
245
|
+
generatedAt: "2026-05-28T15:07:29Z",
|
|
246
|
+
};
|
|
247
|
+
expect(CatV1_0.Schemas.SubmitResultsResponse.parse(response)).toBeDefined();
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it("EndSessionRequest validates session termination", () => {
|
|
251
|
+
const request = {
|
|
252
|
+
sessionId: "550e8400-e29b-41d4-a716-446655440000",
|
|
253
|
+
};
|
|
254
|
+
expect(CatV1_0.Schemas.EndSessionRequest.parse(request)).toBeDefined();
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("EndSessionResponse validates session termination response", () => {
|
|
258
|
+
const response = {
|
|
259
|
+
sessionId: "550e8400-e29b-41d4-a716-446655440000",
|
|
260
|
+
status: "ended",
|
|
261
|
+
};
|
|
262
|
+
expect(CatV1_0.Schemas.EndSessionResponse.parse(response)).toBeDefined();
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it("EndSectionRequest validates section closure", () => {
|
|
266
|
+
const request = {
|
|
267
|
+
sectionId: "550e8400-e29b-41d4-a716-446655440000",
|
|
268
|
+
};
|
|
269
|
+
expect(CatV1_0.Schemas.EndSectionRequest.parse(request)).toBeDefined();
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it("EndSectionResponse validates section closure response", () => {
|
|
273
|
+
const response = {
|
|
274
|
+
sectionId: "550e8400-e29b-41d4-a716-446655440000",
|
|
275
|
+
status: "ended",
|
|
276
|
+
};
|
|
277
|
+
expect(CatV1_0.Schemas.EndSectionResponse.parse(response)).toBeDefined();
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// REST operations catalog test
|
|
282
|
+
describe("REST operation bindings", () => {
|
|
283
|
+
it("REST operation catalog exposes all 6 CAT operations", () => {
|
|
284
|
+
const ops = CatV1_0.RestBinding.Operations;
|
|
285
|
+
expect(ops.createSection).toBeDefined();
|
|
286
|
+
expect(ops.getSection).toBeDefined();
|
|
287
|
+
expect(ops.createSession).toBeDefined();
|
|
288
|
+
expect(ops.submitResults).toBeDefined();
|
|
289
|
+
expect(ops.endSession).toBeDefined();
|
|
290
|
+
expect(ops.endSection).toBeDefined();
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it("CreateSection operation has correct structure", () => {
|
|
294
|
+
const op = CatV1_0.RestBinding.Operations.createSection;
|
|
295
|
+
expect(op.method).toBe("POST");
|
|
296
|
+
expect(op.path).toBe("/sections");
|
|
297
|
+
expect(op.requestPayload).toBeDefined();
|
|
298
|
+
expect(op.successResponsePayload).toBeDefined();
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it("SubmitResults operation has correct path template", () => {
|
|
302
|
+
const op = CatV1_0.RestBinding.Operations.submitResults;
|
|
303
|
+
expect(op.path).toBe("/sessions/{sessionId}/results");
|
|
304
|
+
expect(op.method).toBe("POST");
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
// Derived templates test
|
|
309
|
+
describe("Derived templates", () => {
|
|
310
|
+
it("Cat10DerivedZodTemplates exposes specification metadata", () => {
|
|
311
|
+
const templates = require("../src/cat/v1_0/index").Cat10DerivedZodTemplates;
|
|
312
|
+
expect(templates.description).toBeDefined();
|
|
313
|
+
expect(templates.specLinks).toBeDefined();
|
|
314
|
+
expect(templates.scope).toBeDefined();
|
|
315
|
+
expect(templates.coreEntities).toBeDefined();
|
|
316
|
+
expect(templates.restOperations).toBeDefined();
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it("Specification links point to official CAT v1.0 resources", () => {
|
|
320
|
+
const templates = require("../src/cat/v1_0/index").Cat10DerivedZodTemplates;
|
|
321
|
+
expect(templates.specLinks.main).toContain("imsglobal.org");
|
|
322
|
+
expect(templates.specLinks.main).toContain("cat");
|
|
323
|
+
expect(templates.specLinks.main).toContain("v1p0");
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// Error response test
|
|
328
|
+
describe("Error handling", () => {
|
|
329
|
+
it("ErrorResponse validates error payloads", () => {
|
|
330
|
+
const error = {
|
|
331
|
+
error: "INVALID_SECTION",
|
|
332
|
+
message: "The specified section does not exist",
|
|
333
|
+
statusCode: 404,
|
|
334
|
+
};
|
|
335
|
+
expect(CatV1_0.Schemas.ErrorResponse.parse(error)).toBeDefined();
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
});
|