rahman-resources 0.13.0 → 1.2.0

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.
@@ -1,253 +0,0 @@
1
- // Wave N+3 — consumer-manifest test suite.
2
- //
3
- // Covers: schema validation, semver compare, diffSlice verdict matrix,
4
- // allowedActions gating by syncDirection + generalization, walkConsumerSlices
5
- // against a fixture tree.
6
-
7
- import { describe, it, expect } from "vitest";
8
- import { mkdtemp, mkdir, writeFile, rm } from "node:fs/promises";
9
- import { tmpdir } from "node:os";
10
- import { join } from "node:path";
11
- import {
12
- validateConsumerManifest,
13
- compareSemver,
14
- diffSlice,
15
- walkConsumerSlices,
16
- readConsumerManifest,
17
- writeConsumerManifest,
18
- } from "./consumer-manifest.mjs";
19
-
20
- function baseManifest(over = {}) {
21
- return {
22
- kitabSlug: "comments",
23
- kitabVersion: "0.1.0",
24
- consumerVersion: "0.1.0",
25
- syncDirection: "bidirectional",
26
- generalization: {
27
- status: "portable",
28
- auditedAt: "2026-05-15",
29
- blockers: [],
30
- },
31
- lastPullAt: null,
32
- lastPushAt: null,
33
- ...over,
34
- };
35
- }
36
-
37
- describe("validateConsumerManifest", () => {
38
- it("accepts a minimal valid manifest", () => {
39
- expect(validateConsumerManifest(baseManifest())).toEqual([]);
40
- });
41
-
42
- it("rejects non-kebab slug", () => {
43
- const errs = validateConsumerManifest(baseManifest({ kitabSlug: "Bad_Slug" }));
44
- expect(errs.some((e) => e.includes("kebab-case"))).toBe(true);
45
- });
46
-
47
- it("rejects non-semver versions", () => {
48
- const errs = validateConsumerManifest(baseManifest({ kitabVersion: "v0.1" }));
49
- expect(errs.some((e) => e.includes("semver"))).toBe(true);
50
- });
51
-
52
- it("rejects unknown syncDirection", () => {
53
- const errs = validateConsumerManifest(baseManifest({ syncDirection: "loose" }));
54
- expect(errs.some((e) => e.includes("syncDirection"))).toBe(true);
55
- });
56
-
57
- it("requires blockers when status != portable", () => {
58
- const errs = validateConsumerManifest(
59
- baseManifest({
60
- generalization: {
61
- status: "needs-adapter",
62
- auditedAt: "2026-05-15",
63
- blockers: [],
64
- },
65
- }),
66
- );
67
- expect(errs.some((e) => e.includes("blockers"))).toBe(true);
68
- });
69
-
70
- it("accepts non-portable status with blockers populated", () => {
71
- expect(
72
- validateConsumerManifest(
73
- baseManifest({
74
- generalization: {
75
- status: "consumer-locked",
76
- auditedAt: "2026-05-15",
77
- blockers: ["hardcoded business term"],
78
- },
79
- }),
80
- ),
81
- ).toEqual([]);
82
- });
83
- });
84
-
85
- describe("compareSemver", () => {
86
- it("orders MAJOR.MINOR.PATCH correctly", () => {
87
- expect(compareSemver("0.1.0", "0.1.0")).toBe(0);
88
- expect(compareSemver("0.1.1", "0.1.0")).toBe(1);
89
- expect(compareSemver("0.1.0", "0.1.1")).toBe(-1);
90
- expect(compareSemver("1.0.0", "0.99.99")).toBe(1);
91
- });
92
-
93
- it("strips pre-release/build tags", () => {
94
- expect(compareSemver("0.1.0-beta", "0.1.0+build")).toBe(0);
95
- });
96
- });
97
-
98
- describe("diffSlice verdicts", () => {
99
- it("in-sync when all versions match", () => {
100
- const r = diffSlice({
101
- slug: "comments",
102
- manifest: baseManifest(),
103
- kitabVersion: "0.1.0",
104
- });
105
- expect(r.direction).toBe("in-sync");
106
- expect(r.allowedActions).toEqual([]);
107
- });
108
-
109
- it("up-needed when consumer ahead", () => {
110
- const r = diffSlice({
111
- slug: "comments",
112
- manifest: baseManifest({ consumerVersion: "0.1.3" }),
113
- kitabVersion: "0.1.0",
114
- });
115
- expect(r.direction).toBe("up-needed");
116
- expect(r.allowedActions).toContain("rr-send");
117
- });
118
-
119
- it("down-needed when kitab ahead", () => {
120
- const r = diffSlice({
121
- slug: "comments",
122
- manifest: baseManifest({ consumerVersion: "0.1.0", kitabVersion: "0.1.0" }),
123
- kitabVersion: "0.2.0",
124
- });
125
- expect(r.direction).toBe("down-needed");
126
- expect(r.allowedActions).toContain("rr-update");
127
- });
128
-
129
- it("diverged when both ahead", () => {
130
- const r = diffSlice({
131
- slug: "comments",
132
- manifest: baseManifest({ consumerVersion: "0.1.5", kitabVersion: "0.1.0" }),
133
- kitabVersion: "0.2.0",
134
- });
135
- expect(r.direction).toBe("diverged");
136
- expect(r.allowedActions).toContain("rr-send");
137
- expect(r.allowedActions).toContain("rr-update");
138
- });
139
-
140
- it("kitab-only when no consumer manifest", () => {
141
- const r = diffSlice({ slug: "comments", manifest: null, kitabVersion: "0.1.0" });
142
- expect(r.direction).toBe("kitab-only");
143
- expect(r.allowedActions).toEqual(["rr-update"]);
144
- });
145
-
146
- it("consumer-only when not in kitab", () => {
147
- const r = diffSlice({
148
- slug: "ghost",
149
- manifest: baseManifest({ kitabSlug: "ghost" }),
150
- kitabVersion: null,
151
- });
152
- expect(r.direction).toBe("consumer-only");
153
- expect(r.allowedActions).toEqual(["rr-send"]);
154
- });
155
-
156
- it("frozen syncDirection blocks all actions", () => {
157
- const r = diffSlice({
158
- slug: "comments",
159
- manifest: baseManifest({
160
- consumerVersion: "0.1.5",
161
- syncDirection: "frozen",
162
- }),
163
- kitabVersion: "0.2.0",
164
- });
165
- expect(r.direction).toBe("diverged");
166
- expect(r.allowedActions).toEqual([]);
167
- });
168
-
169
- it("consumer-locked status blocks rr-send", () => {
170
- const r = diffSlice({
171
- slug: "comments",
172
- manifest: baseManifest({
173
- consumerVersion: "0.1.5",
174
- generalization: {
175
- status: "consumer-locked",
176
- auditedAt: "2026-05-15",
177
- blockers: ["business-specific table"],
178
- },
179
- }),
180
- kitabVersion: "0.1.0",
181
- });
182
- expect(r.direction).toBe("up-needed");
183
- expect(r.allowedActions).not.toContain("rr-send");
184
- });
185
-
186
- it("down-only direction blocks rr-send even when up-needed", () => {
187
- const r = diffSlice({
188
- slug: "comments",
189
- manifest: baseManifest({
190
- consumerVersion: "0.1.5",
191
- syncDirection: "down-only",
192
- }),
193
- kitabVersion: "0.1.0",
194
- });
195
- expect(r.direction).toBe("up-needed");
196
- expect(r.allowedActions).not.toContain("rr-send");
197
- });
198
- });
199
-
200
- describe("walkConsumerSlices + read/write round-trip", () => {
201
- it("reads valid manifests and surfaces parse errors per slice", async () => {
202
- const root = await mkdtemp(join(tmpdir(), "rr-bsdl-"));
203
- try {
204
- const slicesDir = join(root, "frontend", "slices");
205
- await mkdir(join(slicesDir, "comments"), { recursive: true });
206
- await mkdir(join(slicesDir, "broken"), { recursive: true });
207
- await mkdir(join(slicesDir, "no-manifest"), { recursive: true });
208
- await mkdir(join(slicesDir, "_internal"), { recursive: true });
209
-
210
- await writeConsumerManifest(
211
- join(slicesDir, "comments", ".kitab.json"),
212
- baseManifest({ kitabSlug: "comments" }),
213
- );
214
- // _internal also has a manifest but should be skipped (underscore prefix)
215
- await writeFile(
216
- join(slicesDir, "_internal", ".kitab.json"),
217
- JSON.stringify(baseManifest({ kitabSlug: "internal" })),
218
- );
219
- await writeFile(
220
- join(slicesDir, "broken", ".kitab.json"),
221
- '{"not": "valid"}',
222
- );
223
-
224
- const walked = await walkConsumerSlices(root);
225
- const slugs = walked.map((w) => w.dir.split("/").pop()).sort();
226
- expect(slugs).toEqual(["broken", "comments"]);
227
- const broken = walked.find((w) => w.dir.endsWith("broken"));
228
- expect(broken.error).toBeDefined();
229
- const ok = walked.find((w) => w.dir.endsWith("comments"));
230
- expect(ok.manifest?.kitabSlug).toBe("comments");
231
-
232
- const round = await readConsumerManifest(
233
- join(slicesDir, "comments", ".kitab.json"),
234
- );
235
- expect(round.kitabSlug).toBe("comments");
236
- expect(round.$schema).toBe(
237
- "https://resource.rahmanef.com/schemas/kitab-consumer.json",
238
- );
239
- } finally {
240
- await rm(root, { recursive: true, force: true });
241
- }
242
- });
243
-
244
- it("returns empty when no slices dir", async () => {
245
- const root = await mkdtemp(join(tmpdir(), "rr-bsdl-empty-"));
246
- try {
247
- const walked = await walkConsumerSlices(root);
248
- expect(walked).toEqual([]);
249
- } finally {
250
- await rm(root, { recursive: true, force: true });
251
- }
252
- });
253
- });