@o3co/auth.policy-verifier.builtins 0.2.0 → 0.3.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.
- package/README.ja.md +101 -0
- package/README.md +101 -0
- package/dist/__tests__/rules/AttrLiteralCompare.test.d.mts +2 -0
- package/dist/__tests__/rules/AttrLiteralCompare.test.d.mts.map +1 -0
- package/dist/__tests__/rules/AttrLiteralCompare.test.mjs +253 -0
- package/dist/__tests__/rules/AttrLiteralEqual.test.d.mts +2 -0
- package/dist/__tests__/rules/AttrLiteralEqual.test.d.mts.map +1 -0
- package/dist/__tests__/rules/AttrLiteralEqual.test.mjs +138 -0
- package/dist/__tests__/rules/AttrLiteralIn.test.d.mts +2 -0
- package/dist/__tests__/rules/AttrLiteralIn.test.d.mts.map +1 -0
- package/dist/__tests__/rules/AttrLiteralIn.test.mjs +160 -0
- package/dist/__tests__/rules/AttrLiteralNotEqual.test.d.mts +2 -0
- package/dist/__tests__/rules/AttrLiteralNotEqual.test.d.mts.map +1 -0
- package/dist/__tests__/rules/AttrLiteralNotEqual.test.mjs +157 -0
- package/dist/__tests__/rules/AttrLiteralNotIn.test.d.mts +2 -0
- package/dist/__tests__/rules/AttrLiteralNotIn.test.d.mts.map +1 -0
- package/dist/__tests__/rules/AttrLiteralNotIn.test.mjs +168 -0
- package/dist/__tests__/rules/AttrMatchRule.test.mjs +8 -0
- package/dist/__tests__/rules/AttrPairCompare.test.d.mts +2 -0
- package/dist/__tests__/rules/AttrPairCompare.test.d.mts.map +1 -0
- package/dist/__tests__/rules/AttrPairCompare.test.mjs +319 -0
- package/dist/__tests__/rules/AttrPairEqual.test.d.mts +2 -0
- package/dist/__tests__/rules/AttrPairEqual.test.d.mts.map +1 -0
- package/dist/__tests__/rules/AttrPairEqual.test.mjs +155 -0
- package/dist/__tests__/rules/AttrPairNotEqual.test.d.mts +2 -0
- package/dist/__tests__/rules/AttrPairNotEqual.test.d.mts.map +1 -0
- package/dist/__tests__/rules/AttrPairNotEqual.test.mjs +155 -0
- package/dist/__tests__/rules/_sharedValidation.test.d.mts +2 -0
- package/dist/__tests__/rules/_sharedValidation.test.d.mts.map +1 -0
- package/dist/__tests__/rules/_sharedValidation.test.mjs +325 -0
- package/dist/index.d.mts +8 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +8 -0
- package/dist/rules/AttrLiteralCompare.d.mts +31 -0
- package/dist/rules/AttrLiteralCompare.d.mts.map +1 -0
- package/dist/rules/AttrLiteralCompare.mjs +36 -0
- package/dist/rules/AttrLiteralEqual.d.mts +37 -0
- package/dist/rules/AttrLiteralEqual.d.mts.map +1 -0
- package/dist/rules/AttrLiteralEqual.mjs +43 -0
- package/dist/rules/AttrLiteralIn.d.mts +38 -0
- package/dist/rules/AttrLiteralIn.d.mts.map +1 -0
- package/dist/rules/AttrLiteralIn.mjs +46 -0
- package/dist/rules/AttrLiteralNotEqual.d.mts +36 -0
- package/dist/rules/AttrLiteralNotEqual.d.mts.map +1 -0
- package/dist/rules/AttrLiteralNotEqual.mjs +42 -0
- package/dist/rules/AttrLiteralNotIn.d.mts +33 -0
- package/dist/rules/AttrLiteralNotIn.d.mts.map +1 -0
- package/dist/rules/AttrLiteralNotIn.mjs +41 -0
- package/dist/rules/AttrMatchRule.d.mts +7 -34
- package/dist/rules/AttrMatchRule.d.mts.map +1 -1
- package/dist/rules/AttrMatchRule.mjs +7 -32
- package/dist/rules/AttrPairCompare.d.mts +35 -0
- package/dist/rules/AttrPairCompare.d.mts.map +1 -0
- package/dist/rules/AttrPairCompare.mjs +43 -0
- package/dist/rules/AttrPairEqual.d.mts +35 -0
- package/dist/rules/AttrPairEqual.d.mts.map +1 -0
- package/dist/rules/AttrPairEqual.mjs +44 -0
- package/dist/rules/AttrPairNotEqual.d.mts +34 -0
- package/dist/rules/AttrPairNotEqual.d.mts.map +1 -0
- package/dist/rules/AttrPairNotEqual.mjs +43 -0
- package/dist/rules/_sharedValidation.d.mts +87 -0
- package/dist/rules/_sharedValidation.d.mts.map +1 -0
- package/dist/rules/_sharedValidation.mjs +189 -0
- package/package.json +5 -2
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { AttrPairCompare } from "#/rules/AttrPairCompare.mjs";
|
|
3
|
+
describe("AttrPairCompare", () => {
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Happy path: each op at boundary values
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
it("op=lt: returns true when attr(a) < attr(b) (5 < 10)", () => {
|
|
8
|
+
const rule = new AttrPairCompare({ a: "low", op: "lt", b: "high" });
|
|
9
|
+
const attrs = new Map([
|
|
10
|
+
["low", 5],
|
|
11
|
+
["high", 10],
|
|
12
|
+
]);
|
|
13
|
+
expect(rule.verify(attrs)).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
it("op=lt: returns false when attr(a) === attr(b) (10 < 10 is false)", () => {
|
|
16
|
+
const rule = new AttrPairCompare({ a: "low", op: "lt", b: "high" });
|
|
17
|
+
const attrs = new Map([
|
|
18
|
+
["low", 10],
|
|
19
|
+
["high", 10],
|
|
20
|
+
]);
|
|
21
|
+
expect(rule.verify(attrs)).toBe(false);
|
|
22
|
+
});
|
|
23
|
+
it("op=lt: returns false when attr(a) > attr(b) (15 < 10 is false)", () => {
|
|
24
|
+
const rule = new AttrPairCompare({ a: "low", op: "lt", b: "high" });
|
|
25
|
+
const attrs = new Map([
|
|
26
|
+
["low", 15],
|
|
27
|
+
["high", 10],
|
|
28
|
+
]);
|
|
29
|
+
expect(rule.verify(attrs)).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
it("op=le: returns true when attr(a) === attr(b) (10 <= 10)", () => {
|
|
32
|
+
const rule = new AttrPairCompare({ a: "low", op: "le", b: "high" });
|
|
33
|
+
const attrs = new Map([
|
|
34
|
+
["low", 10],
|
|
35
|
+
["high", 10],
|
|
36
|
+
]);
|
|
37
|
+
expect(rule.verify(attrs)).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
it("op=le: returns true when attr(a) < attr(b) (5 <= 10)", () => {
|
|
40
|
+
const rule = new AttrPairCompare({ a: "low", op: "le", b: "high" });
|
|
41
|
+
const attrs = new Map([
|
|
42
|
+
["low", 5],
|
|
43
|
+
["high", 10],
|
|
44
|
+
]);
|
|
45
|
+
expect(rule.verify(attrs)).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
it("op=le: returns false when attr(a) > attr(b) (15 <= 10 is false)", () => {
|
|
48
|
+
const rule = new AttrPairCompare({ a: "low", op: "le", b: "high" });
|
|
49
|
+
const attrs = new Map([
|
|
50
|
+
["low", 15],
|
|
51
|
+
["high", 10],
|
|
52
|
+
]);
|
|
53
|
+
expect(rule.verify(attrs)).toBe(false);
|
|
54
|
+
});
|
|
55
|
+
it("op=gt: returns true when attr(a) > attr(b) (15 > 10)", () => {
|
|
56
|
+
const rule = new AttrPairCompare({ a: "score", op: "gt", b: "threshold" });
|
|
57
|
+
const attrs = new Map([
|
|
58
|
+
["score", 15],
|
|
59
|
+
["threshold", 10],
|
|
60
|
+
]);
|
|
61
|
+
expect(rule.verify(attrs)).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
it("op=gt: returns false when attr(a) === attr(b) (10 > 10 is false)", () => {
|
|
64
|
+
const rule = new AttrPairCompare({ a: "score", op: "gt", b: "threshold" });
|
|
65
|
+
const attrs = new Map([
|
|
66
|
+
["score", 10],
|
|
67
|
+
["threshold", 10],
|
|
68
|
+
]);
|
|
69
|
+
expect(rule.verify(attrs)).toBe(false);
|
|
70
|
+
});
|
|
71
|
+
it("op=gt: returns false when attr(a) < attr(b) (5 > 10 is false)", () => {
|
|
72
|
+
const rule = new AttrPairCompare({ a: "score", op: "gt", b: "threshold" });
|
|
73
|
+
const attrs = new Map([
|
|
74
|
+
["score", 5],
|
|
75
|
+
["threshold", 10],
|
|
76
|
+
]);
|
|
77
|
+
expect(rule.verify(attrs)).toBe(false);
|
|
78
|
+
});
|
|
79
|
+
it("op=ge: returns true when attr(a) === attr(b) (10 >= 10)", () => {
|
|
80
|
+
const rule = new AttrPairCompare({ a: "score", op: "ge", b: "threshold" });
|
|
81
|
+
const attrs = new Map([
|
|
82
|
+
["score", 10],
|
|
83
|
+
["threshold", 10],
|
|
84
|
+
]);
|
|
85
|
+
expect(rule.verify(attrs)).toBe(true);
|
|
86
|
+
});
|
|
87
|
+
it("op=ge: returns true when attr(a) > attr(b) (15 >= 10)", () => {
|
|
88
|
+
const rule = new AttrPairCompare({ a: "score", op: "ge", b: "threshold" });
|
|
89
|
+
const attrs = new Map([
|
|
90
|
+
["score", 15],
|
|
91
|
+
["threshold", 10],
|
|
92
|
+
]);
|
|
93
|
+
expect(rule.verify(attrs)).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
it("op=ge: returns false when attr(a) < attr(b) (5 >= 10 is false)", () => {
|
|
96
|
+
const rule = new AttrPairCompare({ a: "score", op: "ge", b: "threshold" });
|
|
97
|
+
const attrs = new Map([
|
|
98
|
+
["score", 5],
|
|
99
|
+
["threshold", 10],
|
|
100
|
+
]);
|
|
101
|
+
expect(rule.verify(attrs)).toBe(false);
|
|
102
|
+
});
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// Evaluation-time safe-deny: attr is not a number
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
it("returns false when attr(a) is a string (not a number)", () => {
|
|
107
|
+
const rule = new AttrPairCompare({ a: "score", op: "gt", b: "threshold" });
|
|
108
|
+
const attrs = new Map([
|
|
109
|
+
["score", "10"],
|
|
110
|
+
["threshold", 5],
|
|
111
|
+
]);
|
|
112
|
+
expect(rule.verify(attrs)).toBe(false);
|
|
113
|
+
});
|
|
114
|
+
it("returns false when attr(b) is a string (not a number)", () => {
|
|
115
|
+
const rule = new AttrPairCompare({ a: "score", op: "gt", b: "threshold" });
|
|
116
|
+
const attrs = new Map([
|
|
117
|
+
["score", 10],
|
|
118
|
+
["threshold", "5"],
|
|
119
|
+
]);
|
|
120
|
+
expect(rule.verify(attrs)).toBe(false);
|
|
121
|
+
});
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
// Evaluation-time safe-deny: missing attr
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
it("returns false when attr(a) is missing", () => {
|
|
126
|
+
const rule = new AttrPairCompare({ a: "score", op: "gt", b: "threshold" });
|
|
127
|
+
const attrs = new Map([["threshold", 5]]);
|
|
128
|
+
expect(rule.verify(attrs)).toBe(false);
|
|
129
|
+
});
|
|
130
|
+
it("returns false when attr(b) is missing", () => {
|
|
131
|
+
const rule = new AttrPairCompare({ a: "score", op: "gt", b: "threshold" });
|
|
132
|
+
const attrs = new Map([["score", 10]]);
|
|
133
|
+
expect(rule.verify(attrs)).toBe(false);
|
|
134
|
+
});
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
// Evaluation-time: NaN attribute → false for all ops
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
it("op=lt: returns false when attr(a) is NaN", () => {
|
|
139
|
+
const rule = new AttrPairCompare({ a: "score", op: "lt", b: "threshold" });
|
|
140
|
+
const attrs = new Map([
|
|
141
|
+
["score", NaN],
|
|
142
|
+
["threshold", 10],
|
|
143
|
+
]);
|
|
144
|
+
expect(rule.verify(attrs)).toBe(false);
|
|
145
|
+
});
|
|
146
|
+
it("op=le: returns false when attr(a) is NaN", () => {
|
|
147
|
+
const rule = new AttrPairCompare({ a: "score", op: "le", b: "threshold" });
|
|
148
|
+
const attrs = new Map([
|
|
149
|
+
["score", NaN],
|
|
150
|
+
["threshold", 10],
|
|
151
|
+
]);
|
|
152
|
+
expect(rule.verify(attrs)).toBe(false);
|
|
153
|
+
});
|
|
154
|
+
it("op=gt: returns false when attr(a) is NaN", () => {
|
|
155
|
+
const rule = new AttrPairCompare({ a: "score", op: "gt", b: "threshold" });
|
|
156
|
+
const attrs = new Map([
|
|
157
|
+
["score", NaN],
|
|
158
|
+
["threshold", 10],
|
|
159
|
+
]);
|
|
160
|
+
expect(rule.verify(attrs)).toBe(false);
|
|
161
|
+
});
|
|
162
|
+
it("op=ge: returns false when attr(a) is NaN", () => {
|
|
163
|
+
const rule = new AttrPairCompare({ a: "score", op: "ge", b: "threshold" });
|
|
164
|
+
const attrs = new Map([
|
|
165
|
+
["score", NaN],
|
|
166
|
+
["threshold", 10],
|
|
167
|
+
]);
|
|
168
|
+
expect(rule.verify(attrs)).toBe(false);
|
|
169
|
+
});
|
|
170
|
+
it("op=lt: returns false when attr(b) is NaN", () => {
|
|
171
|
+
const rule = new AttrPairCompare({ a: "score", op: "lt", b: "threshold" });
|
|
172
|
+
const attrs = new Map([
|
|
173
|
+
["score", 5],
|
|
174
|
+
["threshold", NaN],
|
|
175
|
+
]);
|
|
176
|
+
expect(rule.verify(attrs)).toBe(false);
|
|
177
|
+
});
|
|
178
|
+
it("op=le: returns false when attr(b) is NaN", () => {
|
|
179
|
+
const rule = new AttrPairCompare({ a: "score", op: "le", b: "threshold" });
|
|
180
|
+
const attrs = new Map([
|
|
181
|
+
["score", 5],
|
|
182
|
+
["threshold", NaN],
|
|
183
|
+
]);
|
|
184
|
+
expect(rule.verify(attrs)).toBe(false);
|
|
185
|
+
});
|
|
186
|
+
it("op=gt: returns false when attr(b) is NaN", () => {
|
|
187
|
+
const rule = new AttrPairCompare({ a: "score", op: "gt", b: "threshold" });
|
|
188
|
+
const attrs = new Map([
|
|
189
|
+
["score", 15],
|
|
190
|
+
["threshold", NaN],
|
|
191
|
+
]);
|
|
192
|
+
expect(rule.verify(attrs)).toBe(false);
|
|
193
|
+
});
|
|
194
|
+
it("op=ge: returns false when attr(b) is NaN", () => {
|
|
195
|
+
const rule = new AttrPairCompare({ a: "score", op: "ge", b: "threshold" });
|
|
196
|
+
const attrs = new Map([
|
|
197
|
+
["score", 15],
|
|
198
|
+
["threshold", NaN],
|
|
199
|
+
]);
|
|
200
|
+
expect(rule.verify(attrs)).toBe(false);
|
|
201
|
+
});
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
// Evaluation-time: Infinity / -Infinity attributes
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
it("op=gt: Infinity > 100 returns true", () => {
|
|
206
|
+
const rule = new AttrPairCompare({ a: "score", op: "gt", b: "threshold" });
|
|
207
|
+
const attrs = new Map([
|
|
208
|
+
["score", Infinity],
|
|
209
|
+
["threshold", 100],
|
|
210
|
+
]);
|
|
211
|
+
expect(rule.verify(attrs)).toBe(true);
|
|
212
|
+
});
|
|
213
|
+
it("op=lt: -Infinity < 0 returns true", () => {
|
|
214
|
+
const rule = new AttrPairCompare({ a: "score", op: "lt", b: "threshold" });
|
|
215
|
+
const attrs = new Map([
|
|
216
|
+
["score", -Infinity],
|
|
217
|
+
["threshold", 0],
|
|
218
|
+
]);
|
|
219
|
+
expect(rule.verify(attrs)).toBe(true);
|
|
220
|
+
});
|
|
221
|
+
it("op=ge: Infinity >= Infinity returns true", () => {
|
|
222
|
+
const rule = new AttrPairCompare({ a: "score", op: "ge", b: "threshold" });
|
|
223
|
+
const attrs = new Map([
|
|
224
|
+
["score", Infinity],
|
|
225
|
+
["threshold", Infinity],
|
|
226
|
+
]);
|
|
227
|
+
expect(rule.verify(attrs)).toBe(true);
|
|
228
|
+
});
|
|
229
|
+
// ---------------------------------------------------------------------------
|
|
230
|
+
// Construction errors: invalid 'a'
|
|
231
|
+
// ---------------------------------------------------------------------------
|
|
232
|
+
it("throws when 'a' is undefined", () => {
|
|
233
|
+
expect(() => new AttrPairCompare({ a: undefined, op: "gt", b: "threshold" })).toThrow(/AttrPairCompare.*'a'.*(non-empty string|got undefined)/);
|
|
234
|
+
});
|
|
235
|
+
it("throws when 'a' is a non-string (number 42)", () => {
|
|
236
|
+
expect(() => new AttrPairCompare({ a: 42, op: "gt", b: "threshold" })).toThrow(/AttrPairCompare.*'a'.*got number/);
|
|
237
|
+
});
|
|
238
|
+
it("throws when 'a' is an empty string", () => {
|
|
239
|
+
expect(() => new AttrPairCompare({ a: "", op: "gt", b: "threshold" })).toThrow(/AttrPairCompare.*'a'.*empty string/);
|
|
240
|
+
});
|
|
241
|
+
// ---------------------------------------------------------------------------
|
|
242
|
+
// Construction errors: invalid 'b'
|
|
243
|
+
// ---------------------------------------------------------------------------
|
|
244
|
+
it("throws when 'b' is undefined", () => {
|
|
245
|
+
expect(() => new AttrPairCompare({ a: "score", op: "gt", b: undefined })).toThrow(/AttrPairCompare.*'b'.*(non-empty string|got undefined)/);
|
|
246
|
+
});
|
|
247
|
+
it("throws when 'b' is a non-string (number 42)", () => {
|
|
248
|
+
expect(() => new AttrPairCompare({ a: "score", op: "gt", b: 42 })).toThrow(/AttrPairCompare.*'b'.*got number/);
|
|
249
|
+
});
|
|
250
|
+
it("throws when 'b' is an empty string", () => {
|
|
251
|
+
expect(() => new AttrPairCompare({ a: "score", op: "gt", b: "" })).toThrow(/AttrPairCompare.*'b'.*empty string/);
|
|
252
|
+
});
|
|
253
|
+
// ---------------------------------------------------------------------------
|
|
254
|
+
// Construction errors: invalid 'op'
|
|
255
|
+
// ---------------------------------------------------------------------------
|
|
256
|
+
it("throws when 'op' is missing (undefined)", () => {
|
|
257
|
+
expect(() => new AttrPairCompare({ a: "score", op: undefined, b: "threshold" })).toThrow(/AttrPairCompare.*'op'/);
|
|
258
|
+
});
|
|
259
|
+
it("throws when 'op' is not a string (number 42)", () => {
|
|
260
|
+
expect(() => new AttrPairCompare({ a: "score", op: 42, b: "threshold" })).toThrow(/AttrPairCompare.*'op'/);
|
|
261
|
+
});
|
|
262
|
+
it("throws when 'op' is an invalid string ('eq')", () => {
|
|
263
|
+
expect(() => new AttrPairCompare({ a: "score", op: "eq", b: "threshold" })).toThrow(/AttrPairCompare.*'op'/);
|
|
264
|
+
});
|
|
265
|
+
// ---------------------------------------------------------------------------
|
|
266
|
+
// Construction errors: invalid 'group'
|
|
267
|
+
// ---------------------------------------------------------------------------
|
|
268
|
+
it("throws when 'group' is present but is a non-string (number 42)", () => {
|
|
269
|
+
expect(() => new AttrPairCompare({
|
|
270
|
+
a: "score",
|
|
271
|
+
op: "gt",
|
|
272
|
+
b: "threshold",
|
|
273
|
+
group: 42,
|
|
274
|
+
})).toThrow(/AttrPairCompare.*'group'.*got number/);
|
|
275
|
+
});
|
|
276
|
+
it("throws when 'group' is present but is an empty string", () => {
|
|
277
|
+
expect(() => new AttrPairCompare({ a: "score", op: "gt", b: "threshold", group: "" })).toThrow(/AttrPairCompare.*'group'.*empty string/);
|
|
278
|
+
});
|
|
279
|
+
// ---------------------------------------------------------------------------
|
|
280
|
+
// ruleType: default derived from a, op, b
|
|
281
|
+
// ---------------------------------------------------------------------------
|
|
282
|
+
it("derives default ruleType from a, op, and b", () => {
|
|
283
|
+
const rule = new AttrPairCompare({ a: "score", op: "gt", b: "threshold" });
|
|
284
|
+
expect(rule.ruleType).toBe("attr_pair_compare:score:gt:threshold");
|
|
285
|
+
});
|
|
286
|
+
it("distinct op or b produce distinct ruleTypes (AND semantics)", () => {
|
|
287
|
+
const r1 = new AttrPairCompare({ a: "score", op: "gt", b: "threshold" });
|
|
288
|
+
const r2 = new AttrPairCompare({ a: "score", op: "ge", b: "threshold" });
|
|
289
|
+
const r3 = new AttrPairCompare({ a: "score", op: "gt", b: "limit" });
|
|
290
|
+
expect(r1.ruleType).not.toBe(r2.ruleType);
|
|
291
|
+
expect(r1.ruleType).not.toBe(r3.ruleType);
|
|
292
|
+
});
|
|
293
|
+
// ---------------------------------------------------------------------------
|
|
294
|
+
// ruleType: group override
|
|
295
|
+
// ---------------------------------------------------------------------------
|
|
296
|
+
it("uses the provided group string as ruleType, overriding the default", () => {
|
|
297
|
+
const r1 = new AttrPairCompare({ a: "score", op: "gt", b: "threshold", group: "score-check" });
|
|
298
|
+
const r2 = new AttrPairCompare({ a: "score", op: "ge", b: "limit", group: "score-check" });
|
|
299
|
+
expect(r1.ruleType).toBe("score-check");
|
|
300
|
+
expect(r2.ruleType).toBe("score-check");
|
|
301
|
+
});
|
|
302
|
+
// ---------------------------------------------------------------------------
|
|
303
|
+
// code and message fields
|
|
304
|
+
// ---------------------------------------------------------------------------
|
|
305
|
+
it("has code 'attr_compare_violated'", () => {
|
|
306
|
+
const rule = new AttrPairCompare({ a: "score", op: "gt", b: "threshold" });
|
|
307
|
+
expect(rule.code).toBe("attr_compare_violated");
|
|
308
|
+
});
|
|
309
|
+
it("message contains a name, op, and b name", () => {
|
|
310
|
+
const rule = new AttrPairCompare({ a: "score", op: "gt", b: "threshold" });
|
|
311
|
+
expect(rule.message).toContain("score");
|
|
312
|
+
expect(rule.message).toContain("gt");
|
|
313
|
+
expect(rule.message).toContain("threshold");
|
|
314
|
+
});
|
|
315
|
+
it("message starts with a capital letter", () => {
|
|
316
|
+
const rule = new AttrPairCompare({ a: "score", op: "gt", b: "threshold" });
|
|
317
|
+
expect(rule.message[0]).toBe(rule.message[0]?.toUpperCase());
|
|
318
|
+
});
|
|
319
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AttrPairEqual.test.d.mts","sourceRoot":"","sources":["../../../src/__tests__/rules/AttrPairEqual.test.mts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { AttrPairEqual } from "#/rules/AttrPairEqual.mjs";
|
|
3
|
+
describe("AttrPairEqual", () => {
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Happy path: verify() returns true
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
it("returns true when both attrs are equal non-empty strings", () => {
|
|
8
|
+
const rule = new AttrPairEqual({ a: "subject", b: "owner" });
|
|
9
|
+
const attrs = new Map([
|
|
10
|
+
["subject", "alice"],
|
|
11
|
+
["owner", "alice"],
|
|
12
|
+
]);
|
|
13
|
+
expect(rule.verify(attrs)).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// verify() returns false
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
it("returns false when the two strings are different", () => {
|
|
19
|
+
const rule = new AttrPairEqual({ a: "subject", b: "owner" });
|
|
20
|
+
const attrs = new Map([
|
|
21
|
+
["subject", "alice"],
|
|
22
|
+
["owner", "bob"],
|
|
23
|
+
]);
|
|
24
|
+
expect(rule.verify(attrs)).toBe(false);
|
|
25
|
+
});
|
|
26
|
+
it("returns false when attr 'a' is an empty string", () => {
|
|
27
|
+
const rule = new AttrPairEqual({ a: "subject", b: "owner" });
|
|
28
|
+
const attrs = new Map([
|
|
29
|
+
["subject", ""],
|
|
30
|
+
["owner", "alice"],
|
|
31
|
+
]);
|
|
32
|
+
expect(rule.verify(attrs)).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
it("returns false when attr 'b' is an empty string", () => {
|
|
35
|
+
const rule = new AttrPairEqual({ a: "subject", b: "owner" });
|
|
36
|
+
const attrs = new Map([
|
|
37
|
+
["subject", "alice"],
|
|
38
|
+
["owner", ""],
|
|
39
|
+
]);
|
|
40
|
+
expect(rule.verify(attrs)).toBe(false);
|
|
41
|
+
});
|
|
42
|
+
it("returns false when attr 'a' is a number (non-string)", () => {
|
|
43
|
+
const rule = new AttrPairEqual({ a: "subject", b: "owner" });
|
|
44
|
+
const attrs = new Map([
|
|
45
|
+
["subject", 42],
|
|
46
|
+
["owner", "alice"],
|
|
47
|
+
]);
|
|
48
|
+
expect(rule.verify(attrs)).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
it("returns false when attr 'b' is a number (non-string)", () => {
|
|
51
|
+
const rule = new AttrPairEqual({ a: "subject", b: "owner" });
|
|
52
|
+
const attrs = new Map([
|
|
53
|
+
["subject", "alice"],
|
|
54
|
+
["owner", 42],
|
|
55
|
+
]);
|
|
56
|
+
expect(rule.verify(attrs)).toBe(false);
|
|
57
|
+
});
|
|
58
|
+
it("returns false when attr 'a' is missing", () => {
|
|
59
|
+
const rule = new AttrPairEqual({ a: "subject", b: "owner" });
|
|
60
|
+
const attrs = new Map([["owner", "alice"]]);
|
|
61
|
+
expect(rule.verify(attrs)).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
it("returns false when attr 'b' is missing", () => {
|
|
64
|
+
const rule = new AttrPairEqual({ a: "subject", b: "owner" });
|
|
65
|
+
const attrs = new Map([["subject", "alice"]]);
|
|
66
|
+
expect(rule.verify(attrs)).toBe(false);
|
|
67
|
+
});
|
|
68
|
+
it("returns false when attr 'a' is null", () => {
|
|
69
|
+
const rule = new AttrPairEqual({ a: "subject", b: "owner" });
|
|
70
|
+
const attrs = new Map([
|
|
71
|
+
["subject", null],
|
|
72
|
+
["owner", "alice"],
|
|
73
|
+
]);
|
|
74
|
+
expect(rule.verify(attrs)).toBe(false);
|
|
75
|
+
});
|
|
76
|
+
it("returns false when attr 'b' is null", () => {
|
|
77
|
+
const rule = new AttrPairEqual({ a: "subject", b: "owner" });
|
|
78
|
+
const attrs = new Map([
|
|
79
|
+
["subject", "alice"],
|
|
80
|
+
["owner", null],
|
|
81
|
+
]);
|
|
82
|
+
expect(rule.verify(attrs)).toBe(false);
|
|
83
|
+
});
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// Construction errors: invalid 'a'
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
it("throws when 'a' is undefined", () => {
|
|
88
|
+
expect(() => new AttrPairEqual({ a: undefined, b: "owner" })).toThrow(/AttrPairEqual.*'a'.*(non-empty string|got undefined)/);
|
|
89
|
+
});
|
|
90
|
+
it("throws when 'a' is a non-string (number 42)", () => {
|
|
91
|
+
expect(() => new AttrPairEqual({ a: 42, b: "owner" })).toThrow(/AttrPairEqual.*'a'.*got number/);
|
|
92
|
+
});
|
|
93
|
+
it("throws when 'a' is an empty string", () => {
|
|
94
|
+
expect(() => new AttrPairEqual({ a: "", b: "owner" })).toThrow(/AttrPairEqual.*'a'.*empty string/);
|
|
95
|
+
});
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// Construction errors: invalid 'b'
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
it("throws when 'b' is undefined", () => {
|
|
100
|
+
expect(() => new AttrPairEqual({ a: "subject", b: undefined })).toThrow(/AttrPairEqual.*'b'.*(non-empty string|got undefined)/);
|
|
101
|
+
});
|
|
102
|
+
it("throws when 'b' is a non-string (number 42)", () => {
|
|
103
|
+
expect(() => new AttrPairEqual({ a: "subject", b: 42 })).toThrow(/AttrPairEqual.*'b'.*got number/);
|
|
104
|
+
});
|
|
105
|
+
it("throws when 'b' is an empty string", () => {
|
|
106
|
+
expect(() => new AttrPairEqual({ a: "subject", b: "" })).toThrow(/AttrPairEqual.*'b'.*empty string/);
|
|
107
|
+
});
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
// Construction errors: invalid 'group'
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
it("throws when 'group' is present but is a non-string (number 42)", () => {
|
|
112
|
+
expect(() => new AttrPairEqual({ a: "subject", b: "owner", group: 42 })).toThrow(/AttrPairEqual.*'group'.*got number/);
|
|
113
|
+
});
|
|
114
|
+
it("throws when 'group' is present but is an empty string", () => {
|
|
115
|
+
expect(() => new AttrPairEqual({ a: "subject", b: "owner", group: "" })).toThrow(/AttrPairEqual.*'group'.*empty string/);
|
|
116
|
+
});
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
// ruleType: default derived from 'a' and 'b'
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
it("derives default ruleType from a and b", () => {
|
|
121
|
+
const rule = new AttrPairEqual({ a: "subject", b: "owner" });
|
|
122
|
+
expect(rule.ruleType).toBe("attr_pair_equal:subject:owner");
|
|
123
|
+
});
|
|
124
|
+
it("distinct a/b produce distinct default ruleTypes (AND semantics)", () => {
|
|
125
|
+
const r1 = new AttrPairEqual({ a: "subject", b: "owner" });
|
|
126
|
+
const r2 = new AttrPairEqual({ a: "subject", b: "tenant" });
|
|
127
|
+
expect(r1.ruleType).not.toBe(r2.ruleType);
|
|
128
|
+
});
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
// ruleType: group override
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
it("uses the provided group string as ruleType, overriding the default", () => {
|
|
133
|
+
const r1 = new AttrPairEqual({ a: "subject", b: "owner", group: "identity-check" });
|
|
134
|
+
const r2 = new AttrPairEqual({ a: "subject", b: "creator", group: "identity-check" });
|
|
135
|
+
expect(r1.ruleType).toBe("identity-check");
|
|
136
|
+
expect(r2.ruleType).toBe("identity-check");
|
|
137
|
+
});
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
// code and message fields
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
it("has code 'attr_mismatch'", () => {
|
|
142
|
+
const rule = new AttrPairEqual({ a: "subject", b: "owner" });
|
|
143
|
+
expect(rule.code).toBe("attr_mismatch");
|
|
144
|
+
});
|
|
145
|
+
it("message contains 'a' name, 'b' name, and the phrase 'must equal'", () => {
|
|
146
|
+
const rule = new AttrPairEqual({ a: "subject", b: "owner" });
|
|
147
|
+
expect(rule.message).toContain("subject");
|
|
148
|
+
expect(rule.message).toContain("owner");
|
|
149
|
+
expect(rule.message).toContain("must equal");
|
|
150
|
+
});
|
|
151
|
+
it("message starts with a capital letter", () => {
|
|
152
|
+
const rule = new AttrPairEqual({ a: "subject", b: "owner" });
|
|
153
|
+
expect(rule.message[0]).toBe(rule.message[0]?.toUpperCase());
|
|
154
|
+
});
|
|
155
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AttrPairNotEqual.test.d.mts","sourceRoot":"","sources":["../../../src/__tests__/rules/AttrPairNotEqual.test.mts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { AttrPairNotEqual } from "#/rules/AttrPairNotEqual.mjs";
|
|
3
|
+
describe("AttrPairNotEqual", () => {
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Happy path: verify() returns true
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
it("returns true when both attrs are non-empty strings and not equal", () => {
|
|
8
|
+
const rule = new AttrPairNotEqual({ a: "subject", b: "owner" });
|
|
9
|
+
const attrs = new Map([
|
|
10
|
+
["subject", "alice"],
|
|
11
|
+
["owner", "bob"],
|
|
12
|
+
]);
|
|
13
|
+
expect(rule.verify(attrs)).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// verify() returns false
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
it("returns false when the two strings are equal", () => {
|
|
19
|
+
const rule = new AttrPairNotEqual({ a: "subject", b: "owner" });
|
|
20
|
+
const attrs = new Map([
|
|
21
|
+
["subject", "alice"],
|
|
22
|
+
["owner", "alice"],
|
|
23
|
+
]);
|
|
24
|
+
expect(rule.verify(attrs)).toBe(false);
|
|
25
|
+
});
|
|
26
|
+
it("returns false when attr 'a' is an empty string", () => {
|
|
27
|
+
const rule = new AttrPairNotEqual({ a: "subject", b: "owner" });
|
|
28
|
+
const attrs = new Map([
|
|
29
|
+
["subject", ""],
|
|
30
|
+
["owner", "bob"],
|
|
31
|
+
]);
|
|
32
|
+
expect(rule.verify(attrs)).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
it("returns false when attr 'b' is an empty string", () => {
|
|
35
|
+
const rule = new AttrPairNotEqual({ a: "subject", b: "owner" });
|
|
36
|
+
const attrs = new Map([
|
|
37
|
+
["subject", "alice"],
|
|
38
|
+
["owner", ""],
|
|
39
|
+
]);
|
|
40
|
+
expect(rule.verify(attrs)).toBe(false);
|
|
41
|
+
});
|
|
42
|
+
it("returns false when attr 'a' is a number (non-string)", () => {
|
|
43
|
+
const rule = new AttrPairNotEqual({ a: "subject", b: "owner" });
|
|
44
|
+
const attrs = new Map([
|
|
45
|
+
["subject", 42],
|
|
46
|
+
["owner", "bob"],
|
|
47
|
+
]);
|
|
48
|
+
expect(rule.verify(attrs)).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
it("returns false when attr 'b' is a number (non-string)", () => {
|
|
51
|
+
const rule = new AttrPairNotEqual({ a: "subject", b: "owner" });
|
|
52
|
+
const attrs = new Map([
|
|
53
|
+
["subject", "alice"],
|
|
54
|
+
["owner", 42],
|
|
55
|
+
]);
|
|
56
|
+
expect(rule.verify(attrs)).toBe(false);
|
|
57
|
+
});
|
|
58
|
+
it("returns false when attr 'a' is missing", () => {
|
|
59
|
+
const rule = new AttrPairNotEqual({ a: "subject", b: "owner" });
|
|
60
|
+
const attrs = new Map([["owner", "bob"]]);
|
|
61
|
+
expect(rule.verify(attrs)).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
it("returns false when attr 'b' is missing", () => {
|
|
64
|
+
const rule = new AttrPairNotEqual({ a: "subject", b: "owner" });
|
|
65
|
+
const attrs = new Map([["subject", "alice"]]);
|
|
66
|
+
expect(rule.verify(attrs)).toBe(false);
|
|
67
|
+
});
|
|
68
|
+
it("returns false when attr 'a' is null", () => {
|
|
69
|
+
const rule = new AttrPairNotEqual({ a: "subject", b: "owner" });
|
|
70
|
+
const attrs = new Map([
|
|
71
|
+
["subject", null],
|
|
72
|
+
["owner", "bob"],
|
|
73
|
+
]);
|
|
74
|
+
expect(rule.verify(attrs)).toBe(false);
|
|
75
|
+
});
|
|
76
|
+
it("returns false when attr 'b' is null", () => {
|
|
77
|
+
const rule = new AttrPairNotEqual({ a: "subject", b: "owner" });
|
|
78
|
+
const attrs = new Map([
|
|
79
|
+
["subject", "alice"],
|
|
80
|
+
["owner", null],
|
|
81
|
+
]);
|
|
82
|
+
expect(rule.verify(attrs)).toBe(false);
|
|
83
|
+
});
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// Construction errors: invalid 'a'
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
it("throws when 'a' is undefined", () => {
|
|
88
|
+
expect(() => new AttrPairNotEqual({ a: undefined, b: "owner" })).toThrow(/AttrPairNotEqual.*'a'.*(non-empty string|got undefined)/);
|
|
89
|
+
});
|
|
90
|
+
it("throws when 'a' is a non-string (number 42)", () => {
|
|
91
|
+
expect(() => new AttrPairNotEqual({ a: 42, b: "owner" })).toThrow(/AttrPairNotEqual.*'a'.*got number/);
|
|
92
|
+
});
|
|
93
|
+
it("throws when 'a' is an empty string", () => {
|
|
94
|
+
expect(() => new AttrPairNotEqual({ a: "", b: "owner" })).toThrow(/AttrPairNotEqual.*'a'.*empty string/);
|
|
95
|
+
});
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// Construction errors: invalid 'b'
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
it("throws when 'b' is undefined", () => {
|
|
100
|
+
expect(() => new AttrPairNotEqual({ a: "subject", b: undefined })).toThrow(/AttrPairNotEqual.*'b'.*(non-empty string|got undefined)/);
|
|
101
|
+
});
|
|
102
|
+
it("throws when 'b' is a non-string (number 42)", () => {
|
|
103
|
+
expect(() => new AttrPairNotEqual({ a: "subject", b: 42 })).toThrow(/AttrPairNotEqual.*'b'.*got number/);
|
|
104
|
+
});
|
|
105
|
+
it("throws when 'b' is an empty string", () => {
|
|
106
|
+
expect(() => new AttrPairNotEqual({ a: "subject", b: "" })).toThrow(/AttrPairNotEqual.*'b'.*empty string/);
|
|
107
|
+
});
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
// Construction errors: invalid 'group'
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
it("throws when 'group' is present but is a non-string (number 42)", () => {
|
|
112
|
+
expect(() => new AttrPairNotEqual({ a: "subject", b: "owner", group: 42 })).toThrow(/AttrPairNotEqual.*'group'.*got number/);
|
|
113
|
+
});
|
|
114
|
+
it("throws when 'group' is present but is an empty string", () => {
|
|
115
|
+
expect(() => new AttrPairNotEqual({ a: "subject", b: "owner", group: "" })).toThrow(/AttrPairNotEqual.*'group'.*empty string/);
|
|
116
|
+
});
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
// ruleType: default derived from 'a' and 'b'
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
it("derives default ruleType from a and b", () => {
|
|
121
|
+
const rule = new AttrPairNotEqual({ a: "subject", b: "owner" });
|
|
122
|
+
expect(rule.ruleType).toBe("attr_pair_not_equal:subject:owner");
|
|
123
|
+
});
|
|
124
|
+
it("distinct a/b produce distinct default ruleTypes (AND semantics)", () => {
|
|
125
|
+
const r1 = new AttrPairNotEqual({ a: "subject", b: "owner" });
|
|
126
|
+
const r2 = new AttrPairNotEqual({ a: "subject", b: "tenant" });
|
|
127
|
+
expect(r1.ruleType).not.toBe(r2.ruleType);
|
|
128
|
+
});
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
// ruleType: group override
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
it("uses the provided group string as ruleType, overriding the default", () => {
|
|
133
|
+
const r1 = new AttrPairNotEqual({ a: "subject", b: "owner", group: "not-self" });
|
|
134
|
+
const r2 = new AttrPairNotEqual({ a: "subject", b: "creator", group: "not-self" });
|
|
135
|
+
expect(r1.ruleType).toBe("not-self");
|
|
136
|
+
expect(r2.ruleType).toBe("not-self");
|
|
137
|
+
});
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
// code and message fields
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
it("has code 'attr_match'", () => {
|
|
142
|
+
const rule = new AttrPairNotEqual({ a: "subject", b: "owner" });
|
|
143
|
+
expect(rule.code).toBe("attr_match");
|
|
144
|
+
});
|
|
145
|
+
it("message contains 'a' name, 'b' name, and the phrase 'must not equal'", () => {
|
|
146
|
+
const rule = new AttrPairNotEqual({ a: "subject", b: "owner" });
|
|
147
|
+
expect(rule.message).toContain("subject");
|
|
148
|
+
expect(rule.message).toContain("owner");
|
|
149
|
+
expect(rule.message).toContain("must not equal");
|
|
150
|
+
});
|
|
151
|
+
it("message starts with a capital letter", () => {
|
|
152
|
+
const rule = new AttrPairNotEqual({ a: "subject", b: "owner" });
|
|
153
|
+
expect(rule.message[0]).toBe(rule.message[0]?.toUpperCase());
|
|
154
|
+
});
|
|
155
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_sharedValidation.test.d.mts","sourceRoot":"","sources":["../../../src/__tests__/rules/_sharedValidation.test.mts"],"names":[],"mappings":""}
|