@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,125 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { AdapterCapabilitySchema, AdapterErrorSchema, AdapterProfileSchema } from "../src/adapter";
|
|
3
|
+
import { RunnerConfigSchema } from "../src/config";
|
|
4
|
+
|
|
5
|
+
test("RunnerConfigSchema parses minimal config", () => {
|
|
6
|
+
const parsed = RunnerConfigSchema.parse({
|
|
7
|
+
suite: { name: "lrs", target: "all" },
|
|
8
|
+
sut: { publicUrls: { lrs: "http://127.0.0.1:4001/xapi" } },
|
|
9
|
+
artifacts: { outputDir: "/artifacts" },
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
expect(parsed.contractVersion).toBe("1.0.0");
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test("RunnerConfigSchema requires adapter for cmi5 suite", () => {
|
|
16
|
+
const parsed = RunnerConfigSchema.safeParse({
|
|
17
|
+
suite: { name: "cmi5", target: "all" },
|
|
18
|
+
sut: { publicUrls: { api: "http://127.0.0.1:4301" } },
|
|
19
|
+
artifacts: { outputDir: "/artifacts" },
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
expect(parsed.success).toBe(false);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("RunnerConfigSchema enforces bearer adapter token source", () => {
|
|
26
|
+
const parsed = RunnerConfigSchema.safeParse({
|
|
27
|
+
suite: { name: "cmi5", target: "all" },
|
|
28
|
+
sut: { publicUrls: { api: "http://127.0.0.1:4301" } },
|
|
29
|
+
artifacts: { outputDir: "/artifacts" },
|
|
30
|
+
adapter: {
|
|
31
|
+
kind: "http",
|
|
32
|
+
baseUrl: "http://127.0.0.1:4500",
|
|
33
|
+
auth: { mode: "bearer" },
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
expect(parsed.success).toBe(false);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("AdapterCapabilitySchema validates profile payload", () => {
|
|
41
|
+
const parsed = AdapterCapabilitySchema.safeParse({
|
|
42
|
+
profileVersion: "1.0.0",
|
|
43
|
+
adapterName: "emergent-adapter",
|
|
44
|
+
adapterVersion: "0.1.0",
|
|
45
|
+
profiles: ["cmi5-http-v1"],
|
|
46
|
+
operations: ["package.import", "launch.create", "statements.list"],
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
expect(parsed.success).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("AdapterProfileSchema validates cmi5 interoperability contract", () => {
|
|
53
|
+
const parsed = AdapterProfileSchema.safeParse({
|
|
54
|
+
contractVersion: "1.0.0",
|
|
55
|
+
profileVersion: "1.0.0",
|
|
56
|
+
suite: "cmi5",
|
|
57
|
+
adapter: {
|
|
58
|
+
name: "cmi5-adapter-reference",
|
|
59
|
+
version: "0.1.0",
|
|
60
|
+
transport: "http-json",
|
|
61
|
+
},
|
|
62
|
+
interoperability: {
|
|
63
|
+
statementRetrieval: "adapter-api",
|
|
64
|
+
packageUpload: "inline-base64",
|
|
65
|
+
},
|
|
66
|
+
operations: [
|
|
67
|
+
{
|
|
68
|
+
name: "package.upload.inline-base64",
|
|
69
|
+
path: "/v1/cmi5/packages",
|
|
70
|
+
method: "POST",
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: "statements.list",
|
|
74
|
+
path: "/v1/cmi5/statements",
|
|
75
|
+
method: "GET",
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
artifacts: {
|
|
79
|
+
requirementTraceRequired: true,
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
expect(parsed.success).toBe(true);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("AdapterProfileSchema requires cmi5 packageUpload", () => {
|
|
87
|
+
const parsed = AdapterProfileSchema.safeParse({
|
|
88
|
+
contractVersion: "1.0.0",
|
|
89
|
+
profileVersion: "1.0.0",
|
|
90
|
+
suite: "cmi5",
|
|
91
|
+
adapter: {
|
|
92
|
+
name: "cmi5-adapter-reference",
|
|
93
|
+
version: "0.1.0",
|
|
94
|
+
transport: "http-json",
|
|
95
|
+
},
|
|
96
|
+
interoperability: {
|
|
97
|
+
statementRetrieval: "adapter-api",
|
|
98
|
+
},
|
|
99
|
+
operations: [
|
|
100
|
+
{
|
|
101
|
+
name: "statements.list",
|
|
102
|
+
path: "/v1/cmi5/statements",
|
|
103
|
+
method: "GET",
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
artifacts: {
|
|
107
|
+
requirementTraceRequired: true,
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
expect(parsed.success).toBe(false);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("AdapterErrorSchema validates uniform error envelope", () => {
|
|
115
|
+
const parsed = AdapterErrorSchema.safeParse({
|
|
116
|
+
error: {
|
|
117
|
+
code: "upstream_unavailable",
|
|
118
|
+
message: "Temporary upstream outage",
|
|
119
|
+
category: "upstream_unavailable",
|
|
120
|
+
retriable: true,
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
expect(parsed.success).toBe(true);
|
|
125
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<!-- The following file is a course structure with extension -->
|
|
3
|
+
<courseStructure xmlns="https://w3id.org/xapi/profiles/cmi5/v1/CourseStructure.xsd"
|
|
4
|
+
xmlns:kw="http://www.adlnet.gov/cmi5/KeywordExtension.xsd">
|
|
5
|
+
<course id="http://course-repository.example.edu/identifiers/courses/02baafcf">
|
|
6
|
+
<title>
|
|
7
|
+
<langstring lang="en-US">Introduction to Geology</langstring>
|
|
8
|
+
</title>
|
|
9
|
+
<description>
|
|
10
|
+
<langstring lang="en-US">
|
|
11
|
+
This course will introduce you into the basics of geology. This includes subjects such as
|
|
12
|
+
plate tectonics, geological materials and the history of the Earth.
|
|
13
|
+
</langstring>
|
|
14
|
+
</description>
|
|
15
|
+
</course>
|
|
16
|
+
<au id="http://course-repository.example.edu/identifiers/courses/02baafcf/aus/4c07">
|
|
17
|
+
<title>
|
|
18
|
+
<langstring lang="en-US">Introduction to Geology</langstring>
|
|
19
|
+
</title>
|
|
20
|
+
<description>
|
|
21
|
+
<langstring lang="en-US">
|
|
22
|
+
This course will introduce you into the basics of geology. This includes subjects such as
|
|
23
|
+
plate tectonics, geological materials and the history of the Earth.
|
|
24
|
+
</langstring>
|
|
25
|
+
</description>
|
|
26
|
+
<url>http://course-repository.example.edu/identifiers/courses/02baafcf/aus/4c07/launch.html</url>
|
|
27
|
+
<kw:keyword idref="http://words.example/earth_science"></kw:keyword>
|
|
28
|
+
<kw:keyword idref="http://words.example/plate_tectonics"></kw:keyword>
|
|
29
|
+
<kw:keyword idref="http://words.example/open_course"></kw:keyword>
|
|
30
|
+
<kw:keyword idref="http://words.example/video_based_learning"></kw:keyword>
|
|
31
|
+
<kw:keyword idref="http://words.example/secondary_school"></kw:keyword>
|
|
32
|
+
</au>
|
|
33
|
+
<kw:keywords>
|
|
34
|
+
<kw:keyword id="http://words.example/earth_science">
|
|
35
|
+
<kw:title>
|
|
36
|
+
<langstring lang="en-US">Earth Science</langstring>
|
|
37
|
+
</kw:title>
|
|
38
|
+
<kw:description>
|
|
39
|
+
<langstring lang="en-US">Earth science or geoscience is an all-embracing term referring to the fields of science dealing with planet Earth.</langstring>
|
|
40
|
+
</kw:description>
|
|
41
|
+
</kw:keyword>
|
|
42
|
+
<kw:keyword id="http://words.example/plate_tectonics">
|
|
43
|
+
<kw:title>
|
|
44
|
+
<langstring lang="en-US">Plate Tectonics</langstring>
|
|
45
|
+
</kw:title>
|
|
46
|
+
<kw:description>
|
|
47
|
+
<langstring lang="en-US">Plate tectonics is a scientific theory that describes the motion of Earth's lithosphere.</langstring>
|
|
48
|
+
</kw:description>
|
|
49
|
+
</kw:keyword>
|
|
50
|
+
<kw:keyword id="http://words.example/open_course">
|
|
51
|
+
<kw:title>
|
|
52
|
+
<langstring lang="en-US">Open Course</langstring>
|
|
53
|
+
</kw:title>
|
|
54
|
+
<kw:description>
|
|
55
|
+
<langstring lang="en-US">This course is copyrighted with an open license</langstring>
|
|
56
|
+
</kw:description>
|
|
57
|
+
</kw:keyword>
|
|
58
|
+
<kw:keyword id="http://words.example/video_based_learning">
|
|
59
|
+
<kw:title>
|
|
60
|
+
<langstring lang="en-US">Video Based Learning</langstring>
|
|
61
|
+
</kw:title>
|
|
62
|
+
</kw:keyword>
|
|
63
|
+
<kw:keyword id="http://words.example/secondary_school">
|
|
64
|
+
<kw:title>
|
|
65
|
+
<langstring lang="en-US">Secondary School</langstring>
|
|
66
|
+
</kw:title>
|
|
67
|
+
<kw:description>
|
|
68
|
+
<langstring lang="en-US">The content is prepared for secondary school students</langstring>
|
|
69
|
+
</kw:description>
|
|
70
|
+
</kw:keyword>
|
|
71
|
+
</kw:keywords>
|
|
72
|
+
</courseStructure>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<courseStructure xmlns="https://w3id.org/xapi/profiles/cmi5/v1/CourseStructure.xsd">
|
|
3
|
+
<course id="http://course-repository.example.edu/identifiers/courses/02baafcf">
|
|
4
|
+
<title>
|
|
5
|
+
<langstring lang="en-US">Introduction to Geology</langstring>
|
|
6
|
+
</title>
|
|
7
|
+
<description>
|
|
8
|
+
<langstring lang="en-US">
|
|
9
|
+
This course will introduce you into the basics of geology. This includes subjects such as
|
|
10
|
+
plate tectonics, geological materials and the history of the Earth.
|
|
11
|
+
</langstring>
|
|
12
|
+
</description>
|
|
13
|
+
</course>
|
|
14
|
+
<au id="http://course-repository.example.edu/identifiers/courses/02baafcf/aus/4c07">
|
|
15
|
+
<title>
|
|
16
|
+
<langstring lang="en-US">Introduction to Geology</langstring>
|
|
17
|
+
</title>
|
|
18
|
+
<description>
|
|
19
|
+
<langstring lang="en-US">
|
|
20
|
+
This course will introduce you into the basics of geology. This includes subjects such as
|
|
21
|
+
plate tectonics, geological materials and the history of the Earth.
|
|
22
|
+
</langstring>
|
|
23
|
+
</description>
|
|
24
|
+
<url>http://course-repository.example.edu/identifiers/courses/02baafcf/aus/4c07/launch.html</url>
|
|
25
|
+
</au>
|
|
26
|
+
</courseStructure>
|
package/test/lti.test.ts
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { LtiAgsV2_0, LtiDeepLinkingV2_0, LtiNrpsV2_0, LtiProctoringV1_0, LtiV1_3 } from "@conform-ed/contracts";
|
|
3
|
+
|
|
4
|
+
test("LTI core launch schema accepts a normalized resource link launch", () => {
|
|
5
|
+
const parsed = LtiV1_3.CoreLaunchRequestSchema.safeParse({
|
|
6
|
+
messageType: "LtiResourceLinkRequest",
|
|
7
|
+
version: "1.3.0",
|
|
8
|
+
deploymentId: "deployment-123",
|
|
9
|
+
targetLinkUri: "https://tool.example/launch",
|
|
10
|
+
resourceLink: { id: "resource-123", title: "Example Resource" },
|
|
11
|
+
subject: "student-123",
|
|
12
|
+
context: { id: "course-123", title: "Course 123" },
|
|
13
|
+
roles: ["http://purl.imsglobal.org/vocab/lis/v2/membership#Learner"],
|
|
14
|
+
lis: { courseOfferingSourcedId: "course-123" },
|
|
15
|
+
launchPresentation: { documentTarget: "iframe", locale: "en-US" },
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
expect(parsed.success).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("LTI deep linking schema accepts request settings and response items", () => {
|
|
22
|
+
const request = LtiDeepLinkingV2_0.DeepLinkingRequestSchema.safeParse({
|
|
23
|
+
messageType: "LtiDeepLinkingRequest",
|
|
24
|
+
version: "1.3.0",
|
|
25
|
+
deploymentId: "deployment-123",
|
|
26
|
+
subject: "student-123",
|
|
27
|
+
deepLinkingSettings: {
|
|
28
|
+
deepLinkReturnUrl: "https://platform.example/deep-link/return",
|
|
29
|
+
acceptTypes: ["ltiResourceLink"],
|
|
30
|
+
acceptPresentationDocumentTargets: ["iframe", "window"],
|
|
31
|
+
acceptMediaTypes: "image/*,text/html",
|
|
32
|
+
acceptMultiple: true,
|
|
33
|
+
autoCreate: true,
|
|
34
|
+
title: "Select content",
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
expect(request.success).toBe(true);
|
|
39
|
+
|
|
40
|
+
const response = LtiDeepLinkingV2_0.DeepLinkingResponseSchema.safeParse({
|
|
41
|
+
messageType: "LtiDeepLinkingResponse",
|
|
42
|
+
version: "1.3.0",
|
|
43
|
+
contentItems: [
|
|
44
|
+
{
|
|
45
|
+
type: "ltiResourceLink",
|
|
46
|
+
title: "Example Link",
|
|
47
|
+
url: "https://tool.example/content/alpha",
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
expect(response.success).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("LTI AGS schema accepts endpoint, line item, score, and result shapes", () => {
|
|
56
|
+
expect(
|
|
57
|
+
LtiAgsV2_0.EndpointSchema.safeParse({
|
|
58
|
+
scope: ["https://purl.imsglobal.org/spec/lti-ags/scope/lineitem"],
|
|
59
|
+
lineitems: "https://platform.example/lineitems",
|
|
60
|
+
}).success,
|
|
61
|
+
).toBe(true);
|
|
62
|
+
|
|
63
|
+
expect(
|
|
64
|
+
LtiAgsV2_0.LineItemSchema.safeParse({
|
|
65
|
+
id: "https://platform.example/lineitems/1",
|
|
66
|
+
label: "Example Grade",
|
|
67
|
+
scoreMaximum: 100,
|
|
68
|
+
resourceLinkId: "resource-123",
|
|
69
|
+
}).success,
|
|
70
|
+
).toBe(true);
|
|
71
|
+
|
|
72
|
+
expect(
|
|
73
|
+
LtiAgsV2_0.ScoreSchema.safeParse({
|
|
74
|
+
userId: "student-123",
|
|
75
|
+
scoreGiven: 85,
|
|
76
|
+
scoreMaximum: 100,
|
|
77
|
+
activityProgress: "InProgress",
|
|
78
|
+
gradingProgress: "Pending",
|
|
79
|
+
}).success,
|
|
80
|
+
).toBe(true);
|
|
81
|
+
|
|
82
|
+
expect(
|
|
83
|
+
LtiAgsV2_0.ResultSchema.safeParse({
|
|
84
|
+
userId: "student-123",
|
|
85
|
+
resultScore: 85,
|
|
86
|
+
resultMaximum: 100,
|
|
87
|
+
}).success,
|
|
88
|
+
).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("LTI NRPS schema accepts names/roles service and membership container shapes", () => {
|
|
92
|
+
expect(
|
|
93
|
+
LtiNrpsV2_0.NamesRoleServiceSchema.safeParse({
|
|
94
|
+
contextMembershipsUrl: "https://platform.example/context/2923/memberships",
|
|
95
|
+
serviceVersions: ["2.0"],
|
|
96
|
+
}).success,
|
|
97
|
+
).toBe(true);
|
|
98
|
+
|
|
99
|
+
expect(
|
|
100
|
+
LtiNrpsV2_0.MembershipContainerSchema.safeParse({
|
|
101
|
+
id: "https://platform.example/context/2923/memberships",
|
|
102
|
+
context: {
|
|
103
|
+
id: "2923-abc",
|
|
104
|
+
title: "CPS 435 Learning Analytics",
|
|
105
|
+
},
|
|
106
|
+
members: [
|
|
107
|
+
{
|
|
108
|
+
status: "Active",
|
|
109
|
+
name: "Jane Q. Public",
|
|
110
|
+
email: "jane@example.com",
|
|
111
|
+
userId: "0ae836b9-7fc9-4060-006f-27b2066ac545",
|
|
112
|
+
roles: ["http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor"],
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
}).success,
|
|
116
|
+
).toBe(true);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("LTI proctoring schema accepts start and end assessment messages", () => {
|
|
120
|
+
expect(
|
|
121
|
+
LtiProctoringV1_0.StartProctoringMessageSchema.safeParse({
|
|
122
|
+
messageType: "StartProctoringMessage",
|
|
123
|
+
version: "1.3.0",
|
|
124
|
+
deploymentId: "deployment-123",
|
|
125
|
+
targetLinkUri: "https://tool.example/proctoring",
|
|
126
|
+
resourceLink: { id: "resource-123" },
|
|
127
|
+
subject: "student-123",
|
|
128
|
+
attemptNumber: 2,
|
|
129
|
+
startAssessmentUrl: "https://platform.example/assessment/start",
|
|
130
|
+
sessionData: "session-demo-123",
|
|
131
|
+
roles: ["http://purl.imsglobal.org/vocab/lis/v2/membership#Learner"],
|
|
132
|
+
}).success,
|
|
133
|
+
).toBe(true);
|
|
134
|
+
|
|
135
|
+
expect(
|
|
136
|
+
LtiProctoringV1_0.EndAssessmentMessageSchema.safeParse({
|
|
137
|
+
messageType: "EndAssessmentMessage",
|
|
138
|
+
version: "1.3.0",
|
|
139
|
+
deploymentId: "deployment-123",
|
|
140
|
+
targetLinkUri: "https://tool.example/proctoring",
|
|
141
|
+
subject: "student-123",
|
|
142
|
+
attemptNumber: 2,
|
|
143
|
+
roles: ["http://purl.imsglobal.org/vocab/lis/v2/membership#Learner"],
|
|
144
|
+
}).success,
|
|
145
|
+
).toBe(true);
|
|
146
|
+
});
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { OneRosterV1_2 } from "../src";
|
|
4
|
+
|
|
5
|
+
test("UserSchema parses a minimal OneRoster user", () => {
|
|
6
|
+
const parsed = OneRosterV1_2.UserSchema.safeParse({
|
|
7
|
+
sourcedId: "user-1",
|
|
8
|
+
status: "active",
|
|
9
|
+
dateLastModified: "2025-01-01T00:00:00Z",
|
|
10
|
+
enabledUser: "true",
|
|
11
|
+
givenName: "Ada",
|
|
12
|
+
familyName: "Lovelace",
|
|
13
|
+
roles: [
|
|
14
|
+
{
|
|
15
|
+
roleType: "primary",
|
|
16
|
+
role: "teacher",
|
|
17
|
+
org: {
|
|
18
|
+
href: "https://example.edu/orgs/1",
|
|
19
|
+
sourcedId: "org-1",
|
|
20
|
+
type: "org",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
expect(parsed.success).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("LineItemSchema parses a minimal gradebook line item", () => {
|
|
30
|
+
const parsed = OneRosterV1_2.LineItemSchema.safeParse({
|
|
31
|
+
sourcedId: "line-item-1",
|
|
32
|
+
status: "active",
|
|
33
|
+
dateLastModified: "2025-01-01T00:00:00Z",
|
|
34
|
+
title: "Quiz 1",
|
|
35
|
+
assignDate: "2025-01-10T09:00:00Z",
|
|
36
|
+
dueDate: "2025-01-17T09:00:00Z",
|
|
37
|
+
class: {
|
|
38
|
+
href: "https://example.edu/classes/1",
|
|
39
|
+
sourcedId: "class-1",
|
|
40
|
+
type: "class",
|
|
41
|
+
},
|
|
42
|
+
school: {
|
|
43
|
+
href: "https://example.edu/orgs/1",
|
|
44
|
+
sourcedId: "org-1",
|
|
45
|
+
type: "org",
|
|
46
|
+
},
|
|
47
|
+
category: {
|
|
48
|
+
href: "https://example.edu/categories/1",
|
|
49
|
+
sourcedId: "category-1",
|
|
50
|
+
type: "category",
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
expect(parsed.success).toBe(true);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("ResourceSchema parses a minimal resource entry", () => {
|
|
58
|
+
const parsed = OneRosterV1_2.ResourceSchema.safeParse({
|
|
59
|
+
sourcedId: "resource-1",
|
|
60
|
+
status: "active",
|
|
61
|
+
dateLastModified: "2025-01-01T00:00:00Z",
|
|
62
|
+
vendorResourceId: "vendor-asset-991",
|
|
63
|
+
importance: "primary",
|
|
64
|
+
roles: ["student", "teacher"],
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
expect(parsed.success).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("ImsxStatusInfoSchema parses a OneRoster status envelope", () => {
|
|
71
|
+
const parsed = OneRosterV1_2.ImsxStatusInfoSchema.safeParse({
|
|
72
|
+
imsx_codeMajor: "failure",
|
|
73
|
+
imsx_severity: "error",
|
|
74
|
+
imsx_description: "Unknown object",
|
|
75
|
+
imsx_CodeMinor: {
|
|
76
|
+
imsx_codeMinorField: [
|
|
77
|
+
{
|
|
78
|
+
imsx_codeMinorFieldName: "sourcedId",
|
|
79
|
+
imsx_codeMinorFieldValue: "unknownobject",
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
expect(parsed.success).toBe(true);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("EnrollmentRoleSchema accepts extension values and rejects unknown built-ins", () => {
|
|
89
|
+
expect(OneRosterV1_2.EnrollmentRoleSchema.safeParse("ext:mentor").success).toBe(true);
|
|
90
|
+
expect(OneRosterV1_2.EnrollmentRoleSchema.safeParse("mentor").success).toBe(false);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("ClassSchema requires at least one term reference", () => {
|
|
94
|
+
const parsed = OneRosterV1_2.ClassSchema.safeParse({
|
|
95
|
+
sourcedId: "class-1",
|
|
96
|
+
status: "active",
|
|
97
|
+
dateLastModified: "2025-01-01T00:00:00Z",
|
|
98
|
+
title: "Algebra I",
|
|
99
|
+
course: {
|
|
100
|
+
href: "https://example.edu/courses/1",
|
|
101
|
+
sourcedId: "course-1",
|
|
102
|
+
type: "course",
|
|
103
|
+
},
|
|
104
|
+
school: {
|
|
105
|
+
href: "https://example.edu/orgs/1",
|
|
106
|
+
sourcedId: "org-1",
|
|
107
|
+
type: "org",
|
|
108
|
+
},
|
|
109
|
+
terms: [],
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
expect(parsed.success).toBe(false);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("OneRoster12DerivedZodTemplates exposes core published entry points", () => {
|
|
116
|
+
expect(OneRosterV1_2.OneRoster12DerivedZodTemplates.user).toBe(OneRosterV1_2.UserSchema);
|
|
117
|
+
expect(OneRosterV1_2.OneRoster12DerivedZodTemplates.lineItem).toBe(OneRosterV1_2.LineItemSchema);
|
|
118
|
+
expect(OneRosterV1_2.OneRoster12DerivedZodTemplates.resource).toBe(OneRosterV1_2.ResourceSchema);
|
|
119
|
+
expect(OneRosterV1_2.OneRoster12DerivedZodTemplates.imsxStatusInfo).toBe(OneRosterV1_2.ImsxStatusInfoSchema);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("OneRoster REST binding operation catalogs expose all endpoint schemas", () => {
|
|
123
|
+
expect(Object.keys(OneRosterV1_2.RosteringRestBindingOperationSchemas).length).toBe(41);
|
|
124
|
+
expect(Object.keys(OneRosterV1_2.GradebookRestBindingOperationSchemas).length).toBe(35);
|
|
125
|
+
expect(Object.keys(OneRosterV1_2.ResourceRestBindingOperationSchemas).length).toBe(5);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("Gradebook operation bindings expose request/response payload schemas", () => {
|
|
129
|
+
const binding = OneRosterV1_2.GradebookRestBindingOperationSchemas.postLineItemsForClass;
|
|
130
|
+
|
|
131
|
+
expect(binding.method).toBe("POST");
|
|
132
|
+
expect(binding.path).toBe("/classes/{classSourcedId}/lineItems");
|
|
133
|
+
expect(binding.requestPayload).toBe(OneRosterV1_2.LineItemSetSchema);
|
|
134
|
+
expect(binding.successResponsePayload).toBe(OneRosterV1_2.GuidPairSetSchema);
|
|
135
|
+
expect(binding.errorResponsePayload).toBe(OneRosterV1_2.ImsxStatusInfoSchema);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("ManifestCsvRowSchema accepts either starred or unstarred optional file keys", () => {
|
|
139
|
+
const parsed = OneRosterV1_2.ManifestCsvRowSchema.safeParse({
|
|
140
|
+
"manifest.version": "1.0",
|
|
141
|
+
"oneroster.version": "1.2",
|
|
142
|
+
"file.academicSessions": "bulk",
|
|
143
|
+
"file.categories": "bulk",
|
|
144
|
+
"file.classes": "bulk",
|
|
145
|
+
"file.classResources": "bulk",
|
|
146
|
+
"file.courses": "bulk",
|
|
147
|
+
"file.courseResources": "bulk",
|
|
148
|
+
"file.demographics": "bulk",
|
|
149
|
+
"file.enrollments": "bulk",
|
|
150
|
+
"file.lineItems": "bulk",
|
|
151
|
+
"file.orgs": "bulk",
|
|
152
|
+
"file.resources": "bulk",
|
|
153
|
+
"file.results": "bulk",
|
|
154
|
+
"file.users": "bulk",
|
|
155
|
+
"file.lineItemLearningObjectiveIds*": "bulk",
|
|
156
|
+
"file.lineItemScoreScales": "bulk",
|
|
157
|
+
"file.resultLearningObjectiveIds*": "bulk",
|
|
158
|
+
"file.resultScoreScales": "bulk",
|
|
159
|
+
"file.roles*": "bulk",
|
|
160
|
+
"file.scoreScales": "bulk",
|
|
161
|
+
"file.userProfiles*": "bulk",
|
|
162
|
+
"file.userResources": "bulk",
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
expect(parsed.success).toBe(true);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("UsersCsvRowSchema validates required OneRoster 1.2 user CSV columns", () => {
|
|
169
|
+
const parsed = OneRosterV1_2.UsersCsvRowSchema.safeParse({
|
|
170
|
+
sourcedId: "user-1",
|
|
171
|
+
status: "active",
|
|
172
|
+
dateLastModified: "2025-01-01T00:00:00Z",
|
|
173
|
+
enabledUser: "true",
|
|
174
|
+
username: "ada",
|
|
175
|
+
givenName: "Ada",
|
|
176
|
+
familyName: "Lovelace",
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
expect(parsed.success).toBe(true);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("OneRosterCsvBindingPackageSchema validates package-level CSV document sets", () => {
|
|
183
|
+
const parsed = OneRosterV1_2.OneRosterCsvBindingPackageSchema.safeParse({
|
|
184
|
+
manifest: [
|
|
185
|
+
{
|
|
186
|
+
"manifest.version": "1.0",
|
|
187
|
+
"oneroster.version": "1.2",
|
|
188
|
+
"file.academicSessions": "bulk",
|
|
189
|
+
"file.categories": "bulk",
|
|
190
|
+
"file.classes": "bulk",
|
|
191
|
+
"file.classResources": "bulk",
|
|
192
|
+
"file.courses": "bulk",
|
|
193
|
+
"file.courseResources": "bulk",
|
|
194
|
+
"file.demographics": "bulk",
|
|
195
|
+
"file.enrollments": "bulk",
|
|
196
|
+
"file.lineItems": "bulk",
|
|
197
|
+
"file.orgs": "bulk",
|
|
198
|
+
"file.resources": "bulk",
|
|
199
|
+
"file.results": "bulk",
|
|
200
|
+
"file.users": "bulk",
|
|
201
|
+
"file.lineItemLearningObjectiveIds": "bulk",
|
|
202
|
+
"file.lineItemScoreScales": "bulk",
|
|
203
|
+
"file.resultLearningObjectiveIds": "bulk",
|
|
204
|
+
"file.resultScoreScales": "bulk",
|
|
205
|
+
"file.roles": "bulk",
|
|
206
|
+
"file.scoreScales": "bulk",
|
|
207
|
+
"file.userProfiles": "bulk",
|
|
208
|
+
"file.userResources": "bulk",
|
|
209
|
+
},
|
|
210
|
+
],
|
|
211
|
+
users: [
|
|
212
|
+
{
|
|
213
|
+
sourcedId: "user-1",
|
|
214
|
+
status: "active",
|
|
215
|
+
dateLastModified: "2025-01-01T00:00:00Z",
|
|
216
|
+
enabledUser: "true",
|
|
217
|
+
username: "ada",
|
|
218
|
+
givenName: "Ada",
|
|
219
|
+
familyName: "Lovelace",
|
|
220
|
+
},
|
|
221
|
+
],
|
|
222
|
+
orgs: [
|
|
223
|
+
{
|
|
224
|
+
sourcedId: "org-1",
|
|
225
|
+
status: "active",
|
|
226
|
+
dateLastModified: "2025-01-01T00:00:00Z",
|
|
227
|
+
name: "Example School",
|
|
228
|
+
type: "school",
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
expect(parsed.success).toBe(true);
|
|
234
|
+
});
|