Skip to content

Commit f5a6914

Browse files
add some tests for programsummary
1 parent d9cb623 commit f5a6914

File tree

6 files changed

+379
-20
lines changed

6 files changed

+379
-20
lines changed

frontends/api/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"dependencies": {
3333
"@mitodl/mitxonline-api-axios": "^2025.10.14",
3434
"@tanstack/react-query": "^5.66.0",
35-
"axios": "^1.12.2"
35+
"axios": "^1.12.2",
36+
"tiny-invariant": "^1.3.3"
3637
}
3738
}

frontends/api/src/mitxonline/test-utils/factories/index.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,14 @@ import * as programs from "./programs"
44
import * as courses from "./courses"
55
import * as organizations from "./organization"
66
import * as user from "./user"
7+
import * as requirements from "./requirements"
78

8-
export { mitx as enrollment, programs, courses, organizations, user, pages }
9+
export {
10+
mitx as enrollment,
11+
programs,
12+
courses,
13+
organizations,
14+
user,
15+
pages,
16+
requirements,
17+
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import type {
2+
V2ProgramRequirement,
3+
V2ProgramRequirementData,
4+
} from "@mitodl/mitxonline-api-axios/v2"
5+
import { V2ProgramRequirementDataNodeTypeEnum } from "@mitodl/mitxonline-api-axios/v2"
6+
7+
import { faker } from "@faker-js/faker/locale/en"
8+
import { UniqueEnforcer } from "enforce-unique"
9+
import invariant from "tiny-invariant"
10+
11+
const uniqueNodeId = new UniqueEnforcer()
12+
const uniqueProgramId = new UniqueEnforcer()
13+
14+
type NodeConstructorOpts = {
15+
id?: number | null
16+
data?: Partial<V2ProgramRequirementData>
17+
}
18+
19+
/**
20+
* Build req_tree data for tests:
21+
*
22+
* ```ts
23+
* const root = new RequirementTreeBuilder()
24+
* const required = root.addOperator({ operator: "all_of" })
25+
* const elective = root.addOperator({ operator: "min_number_of", operator_value: "2" })
26+
*
27+
* required // Two required courses
28+
* .addCourse()
29+
* .addCourse()
30+
*
31+
* elective // Three elective courses, two of which must be completed
32+
* .addCourse()
33+
* .addCourse()
34+
* .addCourse()
35+
*
36+
* const req_tree = root.serialize()
37+
* ```
38+
*
39+
*/
40+
class RequirementTreeBuilder implements V2ProgramRequirement {
41+
children: RequirementTreeBuilder[] | undefined
42+
data: V2ProgramRequirementData
43+
id: number
44+
#root: RequirementTreeBuilder
45+
46+
constructor({ id, data }: NodeConstructorOpts = {}) {
47+
this.id = id ?? uniqueNodeId.enforce(faker.number.int)
48+
this.data = {
49+
// @ts-expect-error OpenAPI spec is incorret
50+
node_type: "program_root",
51+
course: null,
52+
required_program: null,
53+
// @ts-expect-error OpenAPI spec is incorret
54+
program: uniqueProgramId.enforce(faker.number.int),
55+
title: null,
56+
operator: null,
57+
operator_value: null,
58+
elective_flag: false,
59+
...data,
60+
}
61+
this.#root = this
62+
}
63+
64+
addChild(node: RequirementTreeBuilder) {
65+
node.#root = this.#root
66+
if (!this.children) {
67+
this.children = []
68+
}
69+
this.children.push(node)
70+
}
71+
72+
addCourse({
73+
course,
74+
}: Pick<Partial<V2ProgramRequirement["data"]>, "course"> = {}) {
75+
const data: V2ProgramRequirementData = {
76+
node_type: V2ProgramRequirementDataNodeTypeEnum.Course,
77+
// @ts-expect-error OpenAPI spec is incorret
78+
course: course ?? uniqueProgramId.enforce(faker.number.int),
79+
program: this.#root.data.program,
80+
required_program: null,
81+
title: null,
82+
operator: null,
83+
operator_value: null,
84+
elective_flag: false,
85+
}
86+
const courseNode = new RequirementTreeBuilder({ data })
87+
this.addChild(courseNode)
88+
return courseNode
89+
}
90+
addOperator(opts: {
91+
operator: "min_number_of" | "all_of"
92+
operator_value?: string
93+
}) {
94+
invariant(opts.operator, "operator is required")
95+
if (opts.operator === "min_number_of") {
96+
invariant(
97+
opts.operator_value &&
98+
!isNaN(Number(opts.operator_value)) &&
99+
Number(opts.operator_value) > 0,
100+
"operator_value is required and must be a positive number when operator is min_number_of",
101+
)
102+
}
103+
const data: V2ProgramRequirementData = {
104+
...opts,
105+
node_type: V2ProgramRequirementDataNodeTypeEnum.Operator,
106+
course: null,
107+
// @ts-expect-error OpenAPI spec is incorret
108+
required_program: null,
109+
program: this.#root.data.program,
110+
title: null,
111+
elective_flag: opts.operator === "min_number_of" ? true : false,
112+
}
113+
const operatorNode = new RequirementTreeBuilder({ data })
114+
this.addChild(operatorNode)
115+
return operatorNode
116+
}
117+
118+
addProgram(opts: { program?: number; title?: string } = {}) {
119+
const programId = opts.program ?? uniqueProgramId.enforce(faker.number.int)
120+
const data: V2ProgramRequirementData = {
121+
// @ts-expect-error OpenAPI spec is incorret
122+
node_type: "program",
123+
course: null,
124+
program: this.#root.data.program,
125+
required_program: programId,
126+
title: null,
127+
operator: null,
128+
operator_value: null,
129+
elective_flag: false,
130+
}
131+
const programNode = new RequirementTreeBuilder({ data })
132+
this.addChild(programNode)
133+
return programNode
134+
}
135+
136+
serialize(): V2ProgramRequirement {
137+
const node = { id: this.id, data: this.data }
138+
const children = this.children?.map((child) => child.serialize())
139+
return children ? { ...node, children } : node
140+
}
141+
}
142+
143+
export { RequirementTreeBuilder }

0 commit comments

Comments
 (0)