@forwardimpact/libsyntheticrender 0.1.1

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.
@@ -0,0 +1,396 @@
1
+ import { describe, test } from "node:test";
2
+ import assert from "node:assert";
3
+ import { validateCrossContent, ContentValidator } from "../validate.js";
4
+
5
+ /**
6
+ * Build minimal valid entities for testing.
7
+ * @param {object} overrides
8
+ * @returns {object}
9
+ */
10
+ function buildEntities(overrides = {}) {
11
+ return {
12
+ teams: [{ id: "team_a" }, { id: "team_b" }],
13
+ people: [
14
+ {
15
+ name: "Zeus",
16
+ email: "zeus@acme.com",
17
+ github: "zeus-bio",
18
+ team_id: "team_a",
19
+ is_manager: false,
20
+ },
21
+ {
22
+ name: "Athena",
23
+ email: "athena@acme.com",
24
+ github: "athena-bio",
25
+ team_id: "team_b",
26
+ is_manager: true,
27
+ },
28
+ ],
29
+ framework: {
30
+ proficiencies: ["awareness", "foundational", "working"],
31
+ maturities: ["emerging", "developing"],
32
+ capabilities: [],
33
+ behaviours: [],
34
+ disciplines: [],
35
+ drivers: [],
36
+ },
37
+ activity: {
38
+ roster: [{ email: "zeus@acme.com" }, { email: "athena@acme.com" }],
39
+ webhooks: [
40
+ {
41
+ delivery_id: "d1",
42
+ event_type: "push",
43
+ payload: {
44
+ repository: "repo-a",
45
+ sender: { login: "zeus-bio" },
46
+ },
47
+ },
48
+ ],
49
+ activityTeams: [{ getdx_team_id: "gt1", name: "Team A" }],
50
+ snapshots: [
51
+ {
52
+ snapshot_id: "s1",
53
+ scheduled_for: "2024-01-01",
54
+ completed_at: "2024-01-02",
55
+ },
56
+ ],
57
+ scores: [
58
+ {
59
+ snapshot_id: "s1",
60
+ getdx_team_id: "gt1",
61
+ item_id: "code_review",
62
+ score: 75,
63
+ },
64
+ ],
65
+ evidence: [
66
+ {
67
+ skill_id: "javascript",
68
+ proficiency: "working",
69
+ },
70
+ ],
71
+ },
72
+ ...overrides,
73
+ };
74
+ }
75
+
76
+ describe("validateCrossContent", () => {
77
+ describe("pass with valid entities", () => {
78
+ test("returns passed=true for fully valid entities", () => {
79
+ const result = validateCrossContent(buildEntities());
80
+ assert.strictEqual(result.passed, true);
81
+ assert.strictEqual(result.failures, 0);
82
+ assert.ok(result.total > 0);
83
+ });
84
+
85
+ test("returns check results with names and messages", () => {
86
+ const result = validateCrossContent(buildEntities());
87
+ for (const check of result.checks) {
88
+ assert.ok(check.name, "Check must have a name");
89
+ assert.ok(check.message, "Check must have a message");
90
+ assert.strictEqual(typeof check.passed, "boolean");
91
+ }
92
+ });
93
+ });
94
+
95
+ describe("people coverage", () => {
96
+ test("fails when people reference unknown teams", () => {
97
+ const entities = buildEntities({
98
+ people: [
99
+ {
100
+ name: "Zeus",
101
+ email: "zeus@acme.com",
102
+ github: "zeus-bio",
103
+ team_id: "nonexistent_team",
104
+ is_manager: false,
105
+ },
106
+ ],
107
+ });
108
+ const result = validateCrossContent(entities);
109
+ const check = result.checks.find((c) => c.name === "people_coverage");
110
+ assert.strictEqual(check.passed, false);
111
+ assert.ok(check.message.includes("unknown teams"));
112
+ });
113
+ });
114
+
115
+ describe("team assignments", () => {
116
+ test("fails when a team has no members", () => {
117
+ const entities = buildEntities({
118
+ teams: [{ id: "team_a" }, { id: "team_b" }, { id: "empty_team" }],
119
+ });
120
+ const result = validateCrossContent(entities);
121
+ const check = result.checks.find((c) => c.name === "team_assignments");
122
+ assert.strictEqual(check.passed, false);
123
+ assert.ok(check.message.includes("no members"));
124
+ });
125
+ });
126
+
127
+ describe("manager references", () => {
128
+ test("fails when manager references unknown team", () => {
129
+ const entities = buildEntities({
130
+ people: [
131
+ {
132
+ name: "Athena",
133
+ email: "athena@acme.com",
134
+ github: "athena-bio",
135
+ team_id: "ghost_team",
136
+ is_manager: true,
137
+ },
138
+ ],
139
+ });
140
+ const result = validateCrossContent(entities);
141
+ const check = result.checks.find((c) => c.name === "manager_references");
142
+ assert.strictEqual(check.passed, false);
143
+ });
144
+ });
145
+
146
+ describe("github usernames", () => {
147
+ test("fails with duplicate github usernames", () => {
148
+ const entities = buildEntities({
149
+ people: [
150
+ {
151
+ name: "Zeus",
152
+ email: "zeus@acme.com",
153
+ github: "same-user",
154
+ team_id: "team_a",
155
+ is_manager: false,
156
+ },
157
+ {
158
+ name: "Athena",
159
+ email: "athena@acme.com",
160
+ github: "same-user",
161
+ team_id: "team_b",
162
+ is_manager: false,
163
+ },
164
+ ],
165
+ });
166
+ const result = validateCrossContent(entities);
167
+ const check = result.checks.find((c) => c.name === "github_usernames");
168
+ assert.strictEqual(check.passed, false);
169
+ assert.ok(check.message.includes("duplicate"));
170
+ });
171
+ });
172
+
173
+ describe("pathway validity", () => {
174
+ test("passes with simple framework config", () => {
175
+ const result = validateCrossContent(buildEntities());
176
+ const check = result.checks.find((c) => c.name === "pathway_validity");
177
+ assert.strictEqual(check.passed, true);
178
+ });
179
+
180
+ test("fails when framework has no proficiencies", () => {
181
+ const entities = buildEntities({
182
+ framework: {
183
+ proficiencies: [],
184
+ capabilities: [],
185
+ behaviours: [],
186
+ disciplines: [],
187
+ drivers: [],
188
+ },
189
+ });
190
+ const result = validateCrossContent(entities);
191
+ const check = result.checks.find((c) => c.name === "pathway_validity");
192
+ assert.strictEqual(check.passed, false);
193
+ });
194
+
195
+ test("fails when discipline references unknown skill (extended mode)", () => {
196
+ const entities = buildEntities({
197
+ framework: {
198
+ proficiencies: ["awareness"],
199
+ capabilities: [{ id: "cap1", name: "Cap", skills: ["javascript"] }],
200
+ behaviours: [{ id: "collab", name: "Collaboration" }],
201
+ disciplines: [
202
+ {
203
+ id: "backend",
204
+ core: ["nonexistent_skill"],
205
+ supporting: [],
206
+ broad: [],
207
+ },
208
+ ],
209
+ drivers: [],
210
+ },
211
+ });
212
+ const result = validateCrossContent(entities);
213
+ const check = result.checks.find((c) => c.name === "pathway_validity");
214
+ assert.strictEqual(check.passed, false);
215
+ assert.ok(check.message.includes("unknown skill"));
216
+ });
217
+
218
+ test("fails when driver references unknown behaviour (extended mode)", () => {
219
+ const entities = buildEntities({
220
+ framework: {
221
+ proficiencies: ["awareness"],
222
+ capabilities: [{ id: "cap1", name: "Cap", skills: ["js"] }],
223
+ behaviours: [{ id: "collab", name: "Collaboration" }],
224
+ disciplines: [],
225
+ drivers: [
226
+ {
227
+ id: "quality",
228
+ skills: ["js"],
229
+ behaviours: ["nonexistent_behaviour"],
230
+ },
231
+ ],
232
+ },
233
+ });
234
+ const result = validateCrossContent(entities);
235
+ const check = result.checks.find((c) => c.name === "pathway_validity");
236
+ assert.strictEqual(check.passed, false);
237
+ assert.ok(check.message.includes("unknown behaviour"));
238
+ });
239
+ });
240
+
241
+ describe("roster completeness", () => {
242
+ test("fails when roster is missing people", () => {
243
+ const entities = buildEntities();
244
+ entities.activity.roster = [{ email: "zeus@acme.com" }];
245
+ const result = validateCrossContent(entities);
246
+ const check = result.checks.find((c) => c.name === "roster_completeness");
247
+ assert.strictEqual(check.passed, false);
248
+ });
249
+ });
250
+
251
+ describe("webhook checks", () => {
252
+ test("fails with invalid webhook schemas", () => {
253
+ const entities = buildEntities();
254
+ entities.activity.webhooks = [
255
+ { delivery_id: "d1" }, // missing event_type, payload
256
+ ];
257
+ const result = validateCrossContent(entities);
258
+ const check = result.checks.find(
259
+ (c) => c.name === "webhook_payload_schemas",
260
+ );
261
+ assert.strictEqual(check.passed, false);
262
+ });
263
+
264
+ test("fails with duplicate webhook delivery IDs", () => {
265
+ const entities = buildEntities();
266
+ entities.activity.webhooks = [
267
+ {
268
+ delivery_id: "dup",
269
+ event_type: "push",
270
+ payload: { repository: "r", sender: { login: "zeus-bio" } },
271
+ },
272
+ {
273
+ delivery_id: "dup",
274
+ event_type: "push",
275
+ payload: { repository: "r", sender: { login: "athena-bio" } },
276
+ },
277
+ ];
278
+ const result = validateCrossContent(entities);
279
+ const check = result.checks.find(
280
+ (c) => c.name === "webhook_delivery_ids",
281
+ );
282
+ assert.strictEqual(check.passed, false);
283
+ });
284
+
285
+ test("fails with unknown webhook sender", () => {
286
+ const entities = buildEntities();
287
+ entities.activity.webhooks = [
288
+ {
289
+ delivery_id: "d1",
290
+ event_type: "push",
291
+ payload: {
292
+ repository: "r",
293
+ sender: { login: "unknown-user" },
294
+ },
295
+ },
296
+ ];
297
+ const result = validateCrossContent(entities);
298
+ const check = result.checks.find(
299
+ (c) => c.name === "webhook_sender_usernames",
300
+ );
301
+ assert.strictEqual(check.passed, false);
302
+ });
303
+ });
304
+
305
+ describe("score and evidence checks", () => {
306
+ test("fails with invalid driver IDs in scores", () => {
307
+ const entities = buildEntities();
308
+ entities.activity.scores = [
309
+ {
310
+ snapshot_id: "s1",
311
+ getdx_team_id: "gt1",
312
+ item_id: "invalid_driver",
313
+ score: 50,
314
+ },
315
+ ];
316
+ const result = validateCrossContent(entities);
317
+ const check = result.checks.find(
318
+ (c) => c.name === "snapshot_score_driver_ids",
319
+ );
320
+ assert.strictEqual(check.passed, false);
321
+ });
322
+
323
+ test("fails with scores out of 0-100 range", () => {
324
+ const entities = buildEntities();
325
+ entities.activity.scores = [
326
+ {
327
+ snapshot_id: "s1",
328
+ getdx_team_id: "gt1",
329
+ item_id: "code_review",
330
+ score: 150,
331
+ },
332
+ ];
333
+ const result = validateCrossContent(entities);
334
+ const check = result.checks.find((c) => c.name === "score_trajectories");
335
+ assert.strictEqual(check.passed, false);
336
+ });
337
+
338
+ test("fails with invalid evidence proficiency", () => {
339
+ const entities = buildEntities();
340
+ entities.activity.evidence = [
341
+ { skill_id: "js", proficiency: "invalid_level" },
342
+ ];
343
+ const result = validateCrossContent(entities);
344
+ const check = result.checks.find(
345
+ (c) => c.name === "evidence_proficiency",
346
+ );
347
+ assert.strictEqual(check.passed, false);
348
+ });
349
+
350
+ test("fails with missing evidence skill IDs", () => {
351
+ const entities = buildEntities();
352
+ entities.activity.evidence = [{ proficiency: "working" }];
353
+ const result = validateCrossContent(entities);
354
+ const check = result.checks.find((c) => c.name === "evidence_skill_ids");
355
+ assert.strictEqual(check.passed, false);
356
+ });
357
+ });
358
+
359
+ describe("result structure", () => {
360
+ test("returns total count matching number of checks", () => {
361
+ const result = validateCrossContent(buildEntities());
362
+ assert.strictEqual(result.total, result.checks.length);
363
+ });
364
+
365
+ test("failures count matches failed checks", () => {
366
+ const entities = buildEntities({
367
+ people: [
368
+ {
369
+ name: "Zeus",
370
+ email: "zeus@acme.com",
371
+ github: "zeus-bio",
372
+ team_id: "nonexistent",
373
+ is_manager: false,
374
+ },
375
+ ],
376
+ });
377
+ const result = validateCrossContent(entities);
378
+ const failedChecks = result.checks.filter((c) => !c.passed);
379
+ assert.strictEqual(result.failures, failedChecks.length);
380
+ assert.strictEqual(result.passed, false);
381
+ });
382
+ });
383
+ });
384
+
385
+ describe("ContentValidator", () => {
386
+ test("throws when logger is not provided", () => {
387
+ assert.throws(() => new ContentValidator(), /logger is required/);
388
+ });
389
+
390
+ test("validates entities using validate method", () => {
391
+ const logger = { info() {}, error() {} };
392
+ const validator = new ContentValidator(logger);
393
+ const result = validator.validate(buildEntities());
394
+ assert.strictEqual(result.passed, true);
395
+ });
396
+ });