@o3co/auth.policy-verifier.builtins 0.3.1 → 0.4.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.
Files changed (128) hide show
  1. package/README.ja.md +70 -14
  2. package/README.md +75 -15
  3. package/dist/collectors/PayloadScopeCollector.d.mts +5 -0
  4. package/dist/collectors/PayloadScopeCollector.d.mts.map +1 -1
  5. package/dist/collectors/PayloadScopeCollector.mjs +8 -1
  6. package/dist/collectors/PayloadSubjectIdCollector.d.mts +9 -0
  7. package/dist/collectors/PayloadSubjectIdCollector.d.mts.map +1 -1
  8. package/dist/collectors/PayloadSubjectIdCollector.mjs +16 -4
  9. package/dist/collectors/RequestContextAttributeCollector.d.mts +47 -0
  10. package/dist/collectors/RequestContextAttributeCollector.d.mts.map +1 -0
  11. package/dist/collectors/RequestContextAttributeCollector.mjs +100 -0
  12. package/dist/collectors/StaticPermissionCollector.d.mts +5 -0
  13. package/dist/collectors/StaticPermissionCollector.d.mts.map +1 -1
  14. package/dist/collectors/StaticPermissionCollector.mjs +7 -0
  15. package/dist/collectors/StaticRoleCollector.d.mts +5 -0
  16. package/dist/collectors/StaticRoleCollector.d.mts.map +1 -1
  17. package/dist/collectors/StaticRoleCollector.mjs +7 -0
  18. package/dist/index.d.mts +3 -2
  19. package/dist/index.d.mts.map +1 -1
  20. package/dist/index.mjs +4 -1
  21. package/dist/module.d.mts +5 -0
  22. package/dist/module.d.mts.map +1 -1
  23. package/dist/module.mjs +10 -1
  24. package/dist/resource/DotNotationResourceParser.d.mts +48 -0
  25. package/dist/resource/DotNotationResourceParser.d.mts.map +1 -1
  26. package/dist/resource/DotNotationResourceParser.mjs +94 -10
  27. package/dist/rules/AttrLiteralCompare.d.mts +2 -2
  28. package/dist/rules/AttrLiteralCompare.d.mts.map +1 -1
  29. package/dist/rules/AttrLiteralCompare.mjs +2 -0
  30. package/dist/rules/AttrLiteralEqual.d.mts +2 -2
  31. package/dist/rules/AttrLiteralEqual.d.mts.map +1 -1
  32. package/dist/rules/AttrLiteralEqual.mjs +2 -0
  33. package/dist/rules/AttrLiteralIn.d.mts +2 -2
  34. package/dist/rules/AttrLiteralIn.d.mts.map +1 -1
  35. package/dist/rules/AttrLiteralIn.mjs +2 -0
  36. package/dist/rules/AttrLiteralNotEqual.d.mts +2 -2
  37. package/dist/rules/AttrLiteralNotEqual.d.mts.map +1 -1
  38. package/dist/rules/AttrLiteralNotEqual.mjs +2 -0
  39. package/dist/rules/AttrLiteralNotIn.d.mts +2 -2
  40. package/dist/rules/AttrLiteralNotIn.d.mts.map +1 -1
  41. package/dist/rules/AttrLiteralNotIn.mjs +2 -0
  42. package/dist/rules/AttrMatchRule.d.mts.map +1 -1
  43. package/dist/rules/AttrMatchRule.mjs +2 -0
  44. package/dist/rules/AttrPairCompare.d.mts +2 -2
  45. package/dist/rules/AttrPairCompare.d.mts.map +1 -1
  46. package/dist/rules/AttrPairCompare.mjs +2 -0
  47. package/dist/rules/AttrPairEqual.d.mts +2 -2
  48. package/dist/rules/AttrPairEqual.d.mts.map +1 -1
  49. package/dist/rules/AttrPairEqual.mjs +2 -0
  50. package/dist/rules/AttrPairNotEqual.d.mts +2 -2
  51. package/dist/rules/AttrPairNotEqual.d.mts.map +1 -1
  52. package/dist/rules/AttrPairNotEqual.mjs +2 -0
  53. package/dist/rules/HasPermission.d.mts +30 -2
  54. package/dist/rules/HasPermission.d.mts.map +1 -1
  55. package/dist/rules/HasPermission.mjs +30 -2
  56. package/dist/rules/HasScope.d.mts +45 -4
  57. package/dist/rules/HasScope.d.mts.map +1 -1
  58. package/dist/rules/HasScope.mjs +46 -9
  59. package/dist/rules/_sharedValidation.d.mts +12 -9
  60. package/dist/rules/_sharedValidation.d.mts.map +1 -1
  61. package/dist/rules/_sharedValidation.mjs +46 -9
  62. package/dist/rules/collectors/ResourceActionPermissionRuleCollector.d.mts +6 -0
  63. package/dist/rules/collectors/ResourceActionPermissionRuleCollector.d.mts.map +1 -1
  64. package/dist/rules/collectors/ResourceActionPermissionRuleCollector.mjs +8 -0
  65. package/dist/rules/collectors/ResourceActionScopeRuleCollector.d.mts +52 -4
  66. package/dist/rules/collectors/ResourceActionScopeRuleCollector.d.mts.map +1 -1
  67. package/dist/rules/collectors/ResourceActionScopeRuleCollector.mjs +54 -6
  68. package/package.json +8 -5
  69. package/dist/__tests__/collectors/PayloadScopeCollector.test.d.mts +0 -2
  70. package/dist/__tests__/collectors/PayloadScopeCollector.test.d.mts.map +0 -1
  71. package/dist/__tests__/collectors/PayloadScopeCollector.test.mjs +0 -24
  72. package/dist/__tests__/collectors/PayloadSubjectIdCollector.test.d.mts +0 -2
  73. package/dist/__tests__/collectors/PayloadSubjectIdCollector.test.d.mts.map +0 -1
  74. package/dist/__tests__/collectors/PayloadSubjectIdCollector.test.mjs +0 -33
  75. package/dist/__tests__/collectors/StaticPermissionCollector.test.d.mts +0 -2
  76. package/dist/__tests__/collectors/StaticPermissionCollector.test.d.mts.map +0 -1
  77. package/dist/__tests__/collectors/StaticPermissionCollector.test.mjs +0 -22
  78. package/dist/__tests__/collectors/StaticRoleCollector.test.d.mts +0 -2
  79. package/dist/__tests__/collectors/StaticRoleCollector.test.d.mts.map +0 -1
  80. package/dist/__tests__/collectors/StaticRoleCollector.test.mjs +0 -24
  81. package/dist/__tests__/module.test.d.mts +0 -2
  82. package/dist/__tests__/module.test.d.mts.map +0 -1
  83. package/dist/__tests__/module.test.mjs +0 -71
  84. package/dist/__tests__/resource/DotNotationResourceParser.test.d.mts +0 -2
  85. package/dist/__tests__/resource/DotNotationResourceParser.test.d.mts.map +0 -1
  86. package/dist/__tests__/resource/DotNotationResourceParser.test.mjs +0 -34
  87. package/dist/__tests__/rules/AttrLiteralCompare.test.d.mts +0 -2
  88. package/dist/__tests__/rules/AttrLiteralCompare.test.d.mts.map +0 -1
  89. package/dist/__tests__/rules/AttrLiteralCompare.test.mjs +0 -253
  90. package/dist/__tests__/rules/AttrLiteralEqual.test.d.mts +0 -2
  91. package/dist/__tests__/rules/AttrLiteralEqual.test.d.mts.map +0 -1
  92. package/dist/__tests__/rules/AttrLiteralEqual.test.mjs +0 -138
  93. package/dist/__tests__/rules/AttrLiteralIn.test.d.mts +0 -2
  94. package/dist/__tests__/rules/AttrLiteralIn.test.d.mts.map +0 -1
  95. package/dist/__tests__/rules/AttrLiteralIn.test.mjs +0 -160
  96. package/dist/__tests__/rules/AttrLiteralNotEqual.test.d.mts +0 -2
  97. package/dist/__tests__/rules/AttrLiteralNotEqual.test.d.mts.map +0 -1
  98. package/dist/__tests__/rules/AttrLiteralNotEqual.test.mjs +0 -157
  99. package/dist/__tests__/rules/AttrLiteralNotIn.test.d.mts +0 -2
  100. package/dist/__tests__/rules/AttrLiteralNotIn.test.d.mts.map +0 -1
  101. package/dist/__tests__/rules/AttrLiteralNotIn.test.mjs +0 -168
  102. package/dist/__tests__/rules/AttrMatchRule.test.d.mts +0 -2
  103. package/dist/__tests__/rules/AttrMatchRule.test.d.mts.map +0 -1
  104. package/dist/__tests__/rules/AttrMatchRule.test.mjs +0 -79
  105. package/dist/__tests__/rules/AttrPairCompare.test.d.mts +0 -2
  106. package/dist/__tests__/rules/AttrPairCompare.test.d.mts.map +0 -1
  107. package/dist/__tests__/rules/AttrPairCompare.test.mjs +0 -319
  108. package/dist/__tests__/rules/AttrPairEqual.test.d.mts +0 -2
  109. package/dist/__tests__/rules/AttrPairEqual.test.d.mts.map +0 -1
  110. package/dist/__tests__/rules/AttrPairEqual.test.mjs +0 -155
  111. package/dist/__tests__/rules/AttrPairNotEqual.test.d.mts +0 -2
  112. package/dist/__tests__/rules/AttrPairNotEqual.test.d.mts.map +0 -1
  113. package/dist/__tests__/rules/AttrPairNotEqual.test.mjs +0 -155
  114. package/dist/__tests__/rules/HasPermission.test.d.mts +0 -2
  115. package/dist/__tests__/rules/HasPermission.test.d.mts.map +0 -1
  116. package/dist/__tests__/rules/HasPermission.test.mjs +0 -86
  117. package/dist/__tests__/rules/HasScope.test.d.mts +0 -2
  118. package/dist/__tests__/rules/HasScope.test.d.mts.map +0 -1
  119. package/dist/__tests__/rules/HasScope.test.mjs +0 -42
  120. package/dist/__tests__/rules/_sharedValidation.test.d.mts +0 -2
  121. package/dist/__tests__/rules/_sharedValidation.test.d.mts.map +0 -1
  122. package/dist/__tests__/rules/_sharedValidation.test.mjs +0 -325
  123. package/dist/__tests__/rules/collectors/ResourceActionPermissionRuleCollector.test.d.mts +0 -2
  124. package/dist/__tests__/rules/collectors/ResourceActionPermissionRuleCollector.test.d.mts.map +0 -1
  125. package/dist/__tests__/rules/collectors/ResourceActionPermissionRuleCollector.test.mjs +0 -27
  126. package/dist/__tests__/rules/collectors/ResourceActionScopeRuleCollector.test.d.mts +0 -2
  127. package/dist/__tests__/rules/collectors/ResourceActionScopeRuleCollector.test.d.mts.map +0 -1
  128. package/dist/__tests__/rules/collectors/ResourceActionScopeRuleCollector.test.mjs +0 -48
@@ -1,71 +0,0 @@
1
- import { Registry, } from "@o3co/auth.policy-verifier.core";
2
- import { describe, expect, it } from "vitest";
3
- import { builtinCollectorsModule } from "../module.mjs";
4
- describe("builtinCollectorsModule", () => {
5
- it("has name 'builtin-collectors'", () => {
6
- expect(builtinCollectorsModule.name).toBe("builtin-collectors");
7
- });
8
- it("registers all builtin attribute collector factories", async () => {
9
- const attributeCollectorRegistry = new Registry();
10
- const ruleCollectorRegistry = new Registry();
11
- const resourceParserRegistry = new Registry();
12
- await builtinCollectorsModule.init({
13
- pathResolver: (s) => s,
14
- config: {},
15
- attributeCollectorRegistry,
16
- ruleCollectorRegistry,
17
- resourceParserRegistry,
18
- });
19
- expect(attributeCollectorRegistry.has("PayloadScopeCollector")).toBe(true);
20
- expect(attributeCollectorRegistry.has("PayloadSubjectIdCollector")).toBe(true);
21
- expect(attributeCollectorRegistry.has("StaticPermissionCollector")).toBe(true);
22
- expect(attributeCollectorRegistry.has("StaticRoleCollector")).toBe(true);
23
- });
24
- it("registers all builtin rule collector factories", async () => {
25
- const attributeCollectorRegistry = new Registry();
26
- const ruleCollectorRegistry = new Registry();
27
- const resourceParserRegistry = new Registry();
28
- await builtinCollectorsModule.init({
29
- pathResolver: (s) => s,
30
- config: {},
31
- attributeCollectorRegistry,
32
- ruleCollectorRegistry,
33
- resourceParserRegistry,
34
- });
35
- expect(ruleCollectorRegistry.has("ResourceActionScopeRuleCollector")).toBe(true);
36
- expect(ruleCollectorRegistry.has("ResourceActionPermissionRuleCollector")).toBe(true);
37
- });
38
- it("registers DotNotationResourceParser factory", async () => {
39
- const attributeCollectorRegistry = new Registry();
40
- const ruleCollectorRegistry = new Registry();
41
- const resourceParserRegistry = new Registry();
42
- await builtinCollectorsModule.init({
43
- pathResolver: (s) => s,
44
- config: {},
45
- attributeCollectorRegistry,
46
- ruleCollectorRegistry,
47
- resourceParserRegistry,
48
- });
49
- expect(resourceParserRegistry.has("DotNotationResourceParser")).toBe(true);
50
- });
51
- it("creates a working StaticPermissionCollector from factory with config", async () => {
52
- const attributeCollectorRegistry = new Registry();
53
- const ruleCollectorRegistry = new Registry();
54
- const resourceParserRegistry = new Registry();
55
- await builtinCollectorsModule.init({
56
- pathResolver: (s) => s,
57
- config: {},
58
- attributeCollectorRegistry,
59
- ruleCollectorRegistry,
60
- resourceParserRegistry,
61
- });
62
- const factory = attributeCollectorRegistry.get("StaticPermissionCollector");
63
- const collector = factory({ permissions: ["admin", "read"] });
64
- const attrs = await collector.collect({
65
- payload: {},
66
- resource: { raw: "test", resourceType: "test" },
67
- action: "read",
68
- });
69
- expect(attrs.get("permissions")).toEqual(["admin", "read"]);
70
- });
71
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=DotNotationResourceParser.test.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"DotNotationResourceParser.test.d.mts","sourceRoot":"","sources":["../../../src/__tests__/resource/DotNotationResourceParser.test.mts"],"names":[],"mappings":""}
@@ -1,34 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { DotNotationResourceParser } from "#/resource/DotNotationResourceParser.mjs";
3
- describe("DotNotationResourceParser", () => {
4
- const parser = new DotNotationResourceParser();
5
- it("parses simple type:id", () => {
6
- const r = parser.parse("project:1");
7
- expect(r.raw).toBe("project:1");
8
- expect(r.resourceType).toBe("project");
9
- expect(r.resourceId).toBe("1");
10
- });
11
- it("parses type-only (no id)", () => {
12
- const r = parser.parse("user");
13
- expect(r.raw).toBe("user");
14
- expect(r.resourceType).toBe("user");
15
- expect(r.resourceId).toBeUndefined();
16
- });
17
- it("parses nested type:id.type:id", () => {
18
- const r = parser.parse("project:1.member:2");
19
- expect(r.raw).toBe("project:1.member:2");
20
- expect(r.resourceType).toBe("project_member");
21
- expect(r.resourceId).toBe("2");
22
- });
23
- it("parses nested type:id.type (last has no id)", () => {
24
- const r = parser.parse("project:1.member");
25
- expect(r.raw).toBe("project:1.member");
26
- expect(r.resourceType).toBe("project_member");
27
- expect(r.resourceId).toBeUndefined();
28
- });
29
- it("trims whitespace", () => {
30
- const r = parser.parse(" project : 1 . member : 2 ");
31
- expect(r.resourceType).toBe("project_member");
32
- expect(r.resourceId).toBe("2");
33
- });
34
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=AttrLiteralCompare.test.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"AttrLiteralCompare.test.d.mts","sourceRoot":"","sources":["../../../src/__tests__/rules/AttrLiteralCompare.test.mts"],"names":[],"mappings":""}
@@ -1,253 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { AttrLiteralCompare } from "#/rules/AttrLiteralCompare.mjs";
3
- describe("AttrLiteralCompare", () => {
4
- // ---------------------------------------------------------------------------
5
- // Happy path: each op at boundary values
6
- // ---------------------------------------------------------------------------
7
- it("op=lt: returns true when attr < v (5 < 10)", () => {
8
- const rule = new AttrLiteralCompare({ a: "score", op: "lt", v: 10 });
9
- const attrs = new Map([["score", 5]]);
10
- expect(rule.verify(attrs)).toBe(true);
11
- });
12
- it("op=lt: returns false when attr === v (10 < 10 is false)", () => {
13
- const rule = new AttrLiteralCompare({ a: "score", op: "lt", v: 10 });
14
- const attrs = new Map([["score", 10]]);
15
- expect(rule.verify(attrs)).toBe(false);
16
- });
17
- it("op=lt: returns false when attr > v (15 < 10 is false)", () => {
18
- const rule = new AttrLiteralCompare({ a: "score", op: "lt", v: 10 });
19
- const attrs = new Map([["score", 15]]);
20
- expect(rule.verify(attrs)).toBe(false);
21
- });
22
- it("op=le: returns true when attr === v (10 <= 10)", () => {
23
- const rule = new AttrLiteralCompare({ a: "score", op: "le", v: 10 });
24
- const attrs = new Map([["score", 10]]);
25
- expect(rule.verify(attrs)).toBe(true);
26
- });
27
- it("op=le: returns true when attr < v (5 <= 10)", () => {
28
- const rule = new AttrLiteralCompare({ a: "score", op: "le", v: 10 });
29
- const attrs = new Map([["score", 5]]);
30
- expect(rule.verify(attrs)).toBe(true);
31
- });
32
- it("op=le: returns false when attr > v (15 <= 10 is false)", () => {
33
- const rule = new AttrLiteralCompare({ a: "score", op: "le", v: 10 });
34
- const attrs = new Map([["score", 15]]);
35
- expect(rule.verify(attrs)).toBe(false);
36
- });
37
- it("op=gt: returns true when attr > v (15 > 10)", () => {
38
- const rule = new AttrLiteralCompare({ a: "score", op: "gt", v: 10 });
39
- const attrs = new Map([["score", 15]]);
40
- expect(rule.verify(attrs)).toBe(true);
41
- });
42
- it("op=gt: returns false when attr === v (10 > 10 is false)", () => {
43
- const rule = new AttrLiteralCompare({ a: "score", op: "gt", v: 10 });
44
- const attrs = new Map([["score", 10]]);
45
- expect(rule.verify(attrs)).toBe(false);
46
- });
47
- it("op=gt: returns false when attr < v (5 > 10 is false)", () => {
48
- const rule = new AttrLiteralCompare({ a: "score", op: "gt", v: 10 });
49
- const attrs = new Map([["score", 5]]);
50
- expect(rule.verify(attrs)).toBe(false);
51
- });
52
- it("op=ge: returns true when attr === v (10 >= 10)", () => {
53
- const rule = new AttrLiteralCompare({ a: "score", op: "ge", v: 10 });
54
- const attrs = new Map([["score", 10]]);
55
- expect(rule.verify(attrs)).toBe(true);
56
- });
57
- it("op=ge: returns true when attr > v (15 >= 10)", () => {
58
- const rule = new AttrLiteralCompare({ a: "score", op: "ge", v: 10 });
59
- const attrs = new Map([["score", 15]]);
60
- expect(rule.verify(attrs)).toBe(true);
61
- });
62
- it("op=ge: returns false when attr < v (5 >= 10 is false)", () => {
63
- const rule = new AttrLiteralCompare({ a: "score", op: "ge", v: 10 });
64
- const attrs = new Map([["score", 5]]);
65
- expect(rule.verify(attrs)).toBe(false);
66
- });
67
- // ---------------------------------------------------------------------------
68
- // Evaluation-time safe-deny: attr is not a number
69
- // ---------------------------------------------------------------------------
70
- it("returns false when attr is a string (not a number)", () => {
71
- const rule = new AttrLiteralCompare({ a: "score", op: "gt", v: 5 });
72
- const attrs = new Map([["score", "10"]]);
73
- expect(rule.verify(attrs)).toBe(false);
74
- });
75
- it("returns false when attr is a boolean (not a number)", () => {
76
- const rule = new AttrLiteralCompare({ a: "score", op: "gt", v: 0 });
77
- const attrs = new Map([["score", true]]);
78
- expect(rule.verify(attrs)).toBe(false);
79
- });
80
- // ---------------------------------------------------------------------------
81
- // Evaluation-time: NaN attribute → false for all ops
82
- // ---------------------------------------------------------------------------
83
- it("op=lt: returns false when attr is NaN", () => {
84
- const rule = new AttrLiteralCompare({ a: "score", op: "lt", v: 10 });
85
- const attrs = new Map([["score", NaN]]);
86
- expect(rule.verify(attrs)).toBe(false);
87
- });
88
- it("op=le: returns false when attr is NaN", () => {
89
- const rule = new AttrLiteralCompare({ a: "score", op: "le", v: 10 });
90
- const attrs = new Map([["score", NaN]]);
91
- expect(rule.verify(attrs)).toBe(false);
92
- });
93
- it("op=gt: returns false when attr is NaN", () => {
94
- const rule = new AttrLiteralCompare({ a: "score", op: "gt", v: 10 });
95
- const attrs = new Map([["score", NaN]]);
96
- expect(rule.verify(attrs)).toBe(false);
97
- });
98
- it("op=ge: returns false when attr is NaN", () => {
99
- const rule = new AttrLiteralCompare({ a: "score", op: "ge", v: 10 });
100
- const attrs = new Map([["score", NaN]]);
101
- expect(rule.verify(attrs)).toBe(false);
102
- });
103
- // ---------------------------------------------------------------------------
104
- // Evaluation-time: Infinity / -Infinity attributes
105
- // ---------------------------------------------------------------------------
106
- it("op=gt: Infinity > 100 returns true", () => {
107
- const rule = new AttrLiteralCompare({ a: "score", op: "gt", v: 100 });
108
- const attrs = new Map([["score", Infinity]]);
109
- expect(rule.verify(attrs)).toBe(true);
110
- });
111
- it("op=lt: -Infinity < 0 returns true", () => {
112
- const rule = new AttrLiteralCompare({ a: "score", op: "lt", v: 0 });
113
- const attrs = new Map([["score", -Infinity]]);
114
- expect(rule.verify(attrs)).toBe(true);
115
- });
116
- it("op=ge: Infinity >= Infinity returns true", () => {
117
- const rule = new AttrLiteralCompare({ a: "score", op: "ge", v: Infinity });
118
- const attrs = new Map([["score", Infinity]]);
119
- expect(rule.verify(attrs)).toBe(true);
120
- });
121
- // ---------------------------------------------------------------------------
122
- // Evaluation-time safe-deny: missing / null attr
123
- // ---------------------------------------------------------------------------
124
- it("returns false when the target attribute is missing", () => {
125
- const rule = new AttrLiteralCompare({ a: "score", op: "gt", v: 0 });
126
- const attrs = new Map();
127
- expect(rule.verify(attrs)).toBe(false);
128
- });
129
- it("returns false when the target attribute is null", () => {
130
- const rule = new AttrLiteralCompare({ a: "score", op: "gt", v: 0 });
131
- const attrs = new Map([["score", null]]);
132
- expect(rule.verify(attrs)).toBe(false);
133
- });
134
- // ---------------------------------------------------------------------------
135
- // Construction errors: invalid 'a'
136
- // ---------------------------------------------------------------------------
137
- it("throws when 'a' is undefined", () => {
138
- expect(() => new AttrLiteralCompare({ a: undefined, op: "gt", v: 0 })).toThrow(/AttrLiteralCompare.*'a'.*(non-empty string|got undefined)/);
139
- });
140
- it("throws when 'a' is an empty string", () => {
141
- expect(() => new AttrLiteralCompare({ a: "", op: "gt", v: 0 })).toThrow(/AttrLiteralCompare.*'a'.*empty string/);
142
- });
143
- it("throws when 'a' is a non-string (number 42)", () => {
144
- expect(() => new AttrLiteralCompare({ a: 42, op: "gt", v: 0 })).toThrow(/AttrLiteralCompare.*'a'.*got number/);
145
- });
146
- // ---------------------------------------------------------------------------
147
- // Construction errors: invalid 'v'
148
- // ---------------------------------------------------------------------------
149
- it("throws when 'v' is missing (undefined)", () => {
150
- expect(() => new AttrLiteralCompare({ a: "score", op: "gt", v: undefined })).toThrow(/AttrLiteralCompare.*'v'/);
151
- });
152
- it("throws when 'v' is null", () => {
153
- expect(() => new AttrLiteralCompare({ a: "score", op: "gt", v: null })).toThrow(/AttrLiteralCompare.*'v'/);
154
- });
155
- it("throws when 'v' is a string", () => {
156
- expect(() => new AttrLiteralCompare({ a: "score", op: "gt", v: "10" })).toThrow(/AttrLiteralCompare.*'v'/);
157
- });
158
- it("throws when 'v' is a boolean", () => {
159
- expect(() => new AttrLiteralCompare({ a: "score", op: "gt", v: true })).toThrow(/AttrLiteralCompare.*'v'/);
160
- });
161
- it("throws when 'v' is a plain object", () => {
162
- expect(() => new AttrLiteralCompare({ a: "score", op: "gt", v: {} })).toThrow(/AttrLiteralCompare.*'v'/);
163
- });
164
- it("throws when 'v' is NaN (fail-fast at construction)", () => {
165
- expect(() => new AttrLiteralCompare({ a: "score", op: "gt", v: NaN })).toThrow(/AttrLiteralCompare.*'v'.*NaN/);
166
- });
167
- // ---------------------------------------------------------------------------
168
- // Construction errors: invalid 'op'
169
- // ---------------------------------------------------------------------------
170
- it("throws when 'op' is missing (undefined)", () => {
171
- expect(() => new AttrLiteralCompare({
172
- a: "score",
173
- op: undefined,
174
- v: 0,
175
- })).toThrow(/AttrLiteralCompare.*'op'/);
176
- });
177
- it("throws when 'op' is not a string (number)", () => {
178
- expect(() => new AttrLiteralCompare({
179
- a: "score",
180
- op: 42,
181
- v: 0,
182
- })).toThrow(/AttrLiteralCompare.*'op'/);
183
- });
184
- it("throws when 'op' is an invalid string ('eq')", () => {
185
- expect(() => new AttrLiteralCompare({
186
- a: "score",
187
- op: "eq",
188
- v: 0,
189
- })).toThrow(/AttrLiteralCompare.*'op'/);
190
- });
191
- // ---------------------------------------------------------------------------
192
- // Construction errors: invalid 'group'
193
- // ---------------------------------------------------------------------------
194
- it("throws when 'group' is present but is an empty string", () => {
195
- expect(() => new AttrLiteralCompare({ a: "score", op: "gt", v: 0, group: "" })).toThrow(/AttrLiteralCompare.*'group'.*empty string/);
196
- });
197
- it("throws when 'group' is present but is a non-string (number 42)", () => {
198
- expect(() => new AttrLiteralCompare({
199
- a: "score",
200
- op: "gt",
201
- v: 0,
202
- group: 42,
203
- })).toThrow(/AttrLiteralCompare.*'group'.*got number/);
204
- });
205
- // ---------------------------------------------------------------------------
206
- // ruleType: default derived from a, op, v
207
- // ---------------------------------------------------------------------------
208
- it("derives default ruleType from a, op, and v", () => {
209
- const rule = new AttrLiteralCompare({ a: "score", op: "gt", v: 10 });
210
- expect(rule.ruleType).toBe("attr_literal_compare:score:gt:10");
211
- });
212
- it("derives default ruleType for negative v", () => {
213
- const rule = new AttrLiteralCompare({ a: "score", op: "lt", v: -5 });
214
- expect(rule.ruleType).toBe("attr_literal_compare:score:lt:-5");
215
- });
216
- it("derives default ruleType for Infinity v", () => {
217
- const rule = new AttrLiteralCompare({ a: "score", op: "lt", v: Infinity });
218
- expect(rule.ruleType).toBe("attr_literal_compare:score:lt:Infinity");
219
- });
220
- it("distinct op or v produce distinct ruleTypes (AND semantics)", () => {
221
- const r1 = new AttrLiteralCompare({ a: "score", op: "gt", v: 10 });
222
- const r2 = new AttrLiteralCompare({ a: "score", op: "ge", v: 10 });
223
- const r3 = new AttrLiteralCompare({ a: "score", op: "gt", v: 20 });
224
- expect(r1.ruleType).not.toBe(r2.ruleType);
225
- expect(r1.ruleType).not.toBe(r3.ruleType);
226
- });
227
- // ---------------------------------------------------------------------------
228
- // ruleType: group override
229
- // ---------------------------------------------------------------------------
230
- it("uses the provided group string as ruleType, overriding the default", () => {
231
- const r1 = new AttrLiteralCompare({ a: "score", op: "gt", v: 10, group: "score-check" });
232
- const r2 = new AttrLiteralCompare({ a: "score", op: "ge", v: 11, group: "score-check" });
233
- expect(r1.ruleType).toBe("score-check");
234
- expect(r2.ruleType).toBe("score-check");
235
- });
236
- // ---------------------------------------------------------------------------
237
- // code and message fields
238
- // ---------------------------------------------------------------------------
239
- it("has code 'attr_compare_violated'", () => {
240
- const rule = new AttrLiteralCompare({ a: "score", op: "gt", v: 10 });
241
- expect(rule.code).toBe("attr_compare_violated");
242
- });
243
- it("message contains the attribute name, op, and v", () => {
244
- const rule = new AttrLiteralCompare({ a: "score", op: "gt", v: 10 });
245
- expect(rule.message).toContain("score");
246
- expect(rule.message).toContain("gt");
247
- expect(rule.message).toContain("10");
248
- });
249
- it("message starts with a capital letter", () => {
250
- const rule = new AttrLiteralCompare({ a: "score", op: "gt", v: 10 });
251
- expect(rule.message[0]).toBe(rule.message[0]?.toUpperCase());
252
- });
253
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=AttrLiteralEqual.test.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"AttrLiteralEqual.test.d.mts","sourceRoot":"","sources":["../../../src/__tests__/rules/AttrLiteralEqual.test.mts"],"names":[],"mappings":""}
@@ -1,138 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { AttrLiteralEqual } from "#/rules/AttrLiteralEqual.mjs";
3
- describe("AttrLiteralEqual", () => {
4
- // ---------------------------------------------------------------------------
5
- // Happy path: verify() returns true
6
- // ---------------------------------------------------------------------------
7
- it("returns true when attr equals the configured string literal", () => {
8
- const rule = new AttrLiteralEqual({ a: "role", v: "admin" });
9
- const attrs = new Map([["role", "admin"]]);
10
- expect(rule.verify(attrs)).toBe(true);
11
- });
12
- it("returns true when attr equals the configured number literal", () => {
13
- const rule = new AttrLiteralEqual({ a: "age", v: 30 });
14
- const attrs = new Map([["age", 30]]);
15
- expect(rule.verify(attrs)).toBe(true);
16
- });
17
- it("returns true when attr equals the configured boolean literal", () => {
18
- const rule = new AttrLiteralEqual({ a: "verified", v: true });
19
- const attrs = new Map([["verified", true]]);
20
- expect(rule.verify(attrs)).toBe(true);
21
- });
22
- // ---------------------------------------------------------------------------
23
- // Type mismatch: no coercion
24
- // ---------------------------------------------------------------------------
25
- it("returns false when number literal v=30 but attr is the string '30' (no coercion)", () => {
26
- const rule = new AttrLiteralEqual({ a: "age", v: 30 });
27
- const attrs = new Map([["age", "30"]]);
28
- expect(rule.verify(attrs)).toBe(false);
29
- });
30
- it("returns false when string literal v='true' but attr is the boolean true (no coercion)", () => {
31
- const rule = new AttrLiteralEqual({ a: "verified", v: "true" });
32
- const attrs = new Map([["verified", true]]);
33
- expect(rule.verify(attrs)).toBe(false);
34
- });
35
- // ---------------------------------------------------------------------------
36
- // Evaluation-time safe-deny: missing / null attr
37
- // ---------------------------------------------------------------------------
38
- it("returns false when the target attribute is missing", () => {
39
- const rule = new AttrLiteralEqual({ a: "role", v: "admin" });
40
- const attrs = new Map();
41
- expect(rule.verify(attrs)).toBe(false);
42
- });
43
- it("returns false when the target attribute is null", () => {
44
- const rule = new AttrLiteralEqual({ a: "role", v: "admin" });
45
- const attrs = new Map([["role", null]]);
46
- expect(rule.verify(attrs)).toBe(false);
47
- });
48
- // ---------------------------------------------------------------------------
49
- // Construction errors: invalid 'a'
50
- // ---------------------------------------------------------------------------
51
- it("throws when 'a' is undefined", () => {
52
- expect(() => new AttrLiteralEqual({ a: undefined, v: "x" })).toThrow(/AttrLiteralEqual.*'a'.*(non-empty string|got undefined)/);
53
- });
54
- it("throws when 'a' is an empty string", () => {
55
- expect(() => new AttrLiteralEqual({ a: "", v: "x" })).toThrow(/AttrLiteralEqual.*'a'.*empty string/);
56
- });
57
- it("throws when 'a' is a non-string (number 42)", () => {
58
- expect(() => new AttrLiteralEqual({ a: 42, v: "x" })).toThrow(/AttrLiteralEqual.*'a'.*got number/);
59
- });
60
- // ---------------------------------------------------------------------------
61
- // Construction errors: invalid 'v'
62
- // ---------------------------------------------------------------------------
63
- it("throws when 'v' is null", () => {
64
- expect(() => new AttrLiteralEqual({ a: "role", v: null })).toThrow(/AttrLiteralEqual.*'v'.*got null/);
65
- });
66
- it("throws when 'v' is undefined", () => {
67
- expect(() => new AttrLiteralEqual({ a: "role", v: undefined })).toThrow(/AttrLiteralEqual.*'v'.*got undefined/);
68
- });
69
- it("throws when 'v' is a plain object ({})", () => {
70
- expect(() => new AttrLiteralEqual({ a: "role", v: {} })).toThrow(/AttrLiteralEqual.*'v'.*got object/);
71
- });
72
- it("throws when 'v' is NaN", () => {
73
- // NaN === NaN is always false, so AttrLiteralEqual({ v: NaN }) would be
74
- // an always-deny rule. Must fail-fast at construction rather than ship
75
- // a silently-broken predicate.
76
- expect(() => new AttrLiteralEqual({ a: "score", v: Number.NaN })).toThrow(/must not be NaN/);
77
- });
78
- // ---------------------------------------------------------------------------
79
- // Construction errors: invalid 'group'
80
- // ---------------------------------------------------------------------------
81
- it("throws when 'group' is present but is an empty string", () => {
82
- expect(() => new AttrLiteralEqual({ a: "role", v: "admin", group: "" })).toThrow(/AttrLiteralEqual.*'group'.*empty string/);
83
- });
84
- it("throws when 'group' is present but is a non-string (number 42)", () => {
85
- expect(() => new AttrLiteralEqual({ a: "role", v: "admin", group: 42 })).toThrow(/AttrLiteralEqual.*'group'.*got number/);
86
- });
87
- // ---------------------------------------------------------------------------
88
- // ruleType: default derived from 'a', 'typeof v', and 'String(v)'
89
- // ---------------------------------------------------------------------------
90
- it("derives default ruleType including the type segment for a string literal", () => {
91
- const rule = new AttrLiteralEqual({ a: "role", v: "admin" });
92
- expect(rule.ruleType).toBe("attr_literal_equal:role:string:admin");
93
- });
94
- it("derives default ruleType including the type segment for a number literal", () => {
95
- const rule = new AttrLiteralEqual({ a: "age", v: 30 });
96
- expect(rule.ruleType).toBe("attr_literal_equal:age:number:30");
97
- });
98
- it("derives default ruleType including the type segment for a boolean literal", () => {
99
- const rule = new AttrLiteralEqual({ a: "verified", v: true });
100
- expect(rule.ruleType).toBe("attr_literal_equal:verified:boolean:true");
101
- });
102
- it("distinct configs produce distinct default ruleTypes (AND semantics)", () => {
103
- const r1 = new AttrLiteralEqual({ a: "role", v: "admin" });
104
- const r2 = new AttrLiteralEqual({ a: "role", v: "editor" });
105
- expect(r1.ruleType).not.toBe(r2.ruleType);
106
- });
107
- it("distinguishes literals by type in ruleType (e.g. boolean true vs string 'true')", () => {
108
- // Regression guard: without the typeof-segment in ruleType, these two
109
- // rules would share the same default ruleType and the evaluator would
110
- // silently OR them together, weakening authorization. The type tag
111
- // keeps them AND-combined as intended.
112
- const r1 = new AttrLiteralEqual({ a: "status", v: true });
113
- const r2 = new AttrLiteralEqual({ a: "status", v: "true" });
114
- expect(r1.ruleType).not.toBe(r2.ruleType);
115
- });
116
- // ---------------------------------------------------------------------------
117
- // ruleType: group override
118
- // ---------------------------------------------------------------------------
119
- it("uses the provided group string as ruleType, overriding the default", () => {
120
- const r1 = new AttrLiteralEqual({ a: "role", v: "admin", group: "any-admin-role" });
121
- const r2 = new AttrLiteralEqual({ a: "role", v: "superuser", group: "any-admin-role" });
122
- expect(r1.ruleType).toBe("any-admin-role");
123
- expect(r2.ruleType).toBe("any-admin-role");
124
- });
125
- // ---------------------------------------------------------------------------
126
- // code and message fields
127
- // ---------------------------------------------------------------------------
128
- it("has code 'attr_not_equal'", () => {
129
- const rule = new AttrLiteralEqual({ a: "role", v: "admin" });
130
- expect(rule.code).toBe("attr_not_equal");
131
- });
132
- it("message contains the attribute name and literal value, and starts with a capital letter", () => {
133
- const rule = new AttrLiteralEqual({ a: "role", v: "admin" });
134
- expect(rule.message).toContain("role");
135
- expect(rule.message).toContain("admin");
136
- expect(rule.message[0]).toBe(rule.message[0]?.toUpperCase());
137
- });
138
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=AttrLiteralIn.test.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"AttrLiteralIn.test.d.mts","sourceRoot":"","sources":["../../../src/__tests__/rules/AttrLiteralIn.test.mts"],"names":[],"mappings":""}