@o3co/auth.policy-verifier.builtins 0.1.1 → 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.
Files changed (79) hide show
  1. package/README.ja.md +116 -2
  2. package/README.md +116 -2
  3. package/dist/__tests__/module.test.mjs +0 -1
  4. package/dist/__tests__/rules/AttrLiteralCompare.test.d.mts +2 -0
  5. package/dist/__tests__/rules/AttrLiteralCompare.test.d.mts.map +1 -0
  6. package/dist/__tests__/rules/AttrLiteralCompare.test.mjs +253 -0
  7. package/dist/__tests__/rules/AttrLiteralEqual.test.d.mts +2 -0
  8. package/dist/__tests__/rules/AttrLiteralEqual.test.d.mts.map +1 -0
  9. package/dist/__tests__/rules/AttrLiteralEqual.test.mjs +138 -0
  10. package/dist/__tests__/rules/AttrLiteralIn.test.d.mts +2 -0
  11. package/dist/__tests__/rules/AttrLiteralIn.test.d.mts.map +1 -0
  12. package/dist/__tests__/rules/AttrLiteralIn.test.mjs +160 -0
  13. package/dist/__tests__/rules/AttrLiteralNotEqual.test.d.mts +2 -0
  14. package/dist/__tests__/rules/AttrLiteralNotEqual.test.d.mts.map +1 -0
  15. package/dist/__tests__/rules/AttrLiteralNotEqual.test.mjs +157 -0
  16. package/dist/__tests__/rules/AttrLiteralNotIn.test.d.mts +2 -0
  17. package/dist/__tests__/rules/AttrLiteralNotIn.test.d.mts.map +1 -0
  18. package/dist/__tests__/rules/AttrLiteralNotIn.test.mjs +168 -0
  19. package/dist/__tests__/rules/AttrMatchRule.test.d.mts +2 -0
  20. package/dist/__tests__/rules/AttrMatchRule.test.d.mts.map +1 -0
  21. package/dist/__tests__/rules/AttrMatchRule.test.mjs +79 -0
  22. package/dist/__tests__/rules/AttrPairCompare.test.d.mts +2 -0
  23. package/dist/__tests__/rules/AttrPairCompare.test.d.mts.map +1 -0
  24. package/dist/__tests__/rules/AttrPairCompare.test.mjs +319 -0
  25. package/dist/__tests__/rules/AttrPairEqual.test.d.mts +2 -0
  26. package/dist/__tests__/rules/AttrPairEqual.test.d.mts.map +1 -0
  27. package/dist/__tests__/rules/AttrPairEqual.test.mjs +155 -0
  28. package/dist/__tests__/rules/AttrPairNotEqual.test.d.mts +2 -0
  29. package/dist/__tests__/rules/AttrPairNotEqual.test.d.mts.map +1 -0
  30. package/dist/__tests__/rules/AttrPairNotEqual.test.mjs +155 -0
  31. package/dist/__tests__/rules/ResourceActionScopeRuleCollector.test.mjs +25 -4
  32. package/dist/__tests__/rules/_sharedValidation.test.d.mts +2 -0
  33. package/dist/__tests__/rules/_sharedValidation.test.d.mts.map +1 -0
  34. package/dist/__tests__/rules/_sharedValidation.test.mjs +325 -0
  35. package/dist/index.d.mts +9 -1
  36. package/dist/index.d.mts.map +1 -1
  37. package/dist/index.mjs +9 -1
  38. package/dist/module.d.mts.map +1 -1
  39. package/dist/module.mjs +0 -2
  40. package/dist/rules/AttrLiteralCompare.d.mts +31 -0
  41. package/dist/rules/AttrLiteralCompare.d.mts.map +1 -0
  42. package/dist/rules/AttrLiteralCompare.mjs +36 -0
  43. package/dist/rules/AttrLiteralEqual.d.mts +37 -0
  44. package/dist/rules/AttrLiteralEqual.d.mts.map +1 -0
  45. package/dist/rules/AttrLiteralEqual.mjs +43 -0
  46. package/dist/rules/AttrLiteralIn.d.mts +38 -0
  47. package/dist/rules/AttrLiteralIn.d.mts.map +1 -0
  48. package/dist/rules/AttrLiteralIn.mjs +46 -0
  49. package/dist/rules/AttrLiteralNotEqual.d.mts +36 -0
  50. package/dist/rules/AttrLiteralNotEqual.d.mts.map +1 -0
  51. package/dist/rules/AttrLiteralNotEqual.mjs +42 -0
  52. package/dist/rules/AttrLiteralNotIn.d.mts +33 -0
  53. package/dist/rules/AttrLiteralNotIn.d.mts.map +1 -0
  54. package/dist/rules/AttrLiteralNotIn.mjs +41 -0
  55. package/dist/rules/AttrMatchRule.d.mts +15 -0
  56. package/dist/rules/AttrMatchRule.d.mts.map +1 -0
  57. package/dist/rules/AttrMatchRule.mjs +17 -0
  58. package/dist/rules/AttrPairCompare.d.mts +35 -0
  59. package/dist/rules/AttrPairCompare.d.mts.map +1 -0
  60. package/dist/rules/AttrPairCompare.mjs +43 -0
  61. package/dist/rules/AttrPairEqual.d.mts +35 -0
  62. package/dist/rules/AttrPairEqual.d.mts.map +1 -0
  63. package/dist/rules/AttrPairEqual.mjs +44 -0
  64. package/dist/rules/AttrPairNotEqual.d.mts +34 -0
  65. package/dist/rules/AttrPairNotEqual.d.mts.map +1 -0
  66. package/dist/rules/AttrPairNotEqual.mjs +43 -0
  67. package/dist/rules/ResourceActionScopeRuleCollector.d.mts +9 -0
  68. package/dist/rules/ResourceActionScopeRuleCollector.d.mts.map +1 -1
  69. package/dist/rules/ResourceActionScopeRuleCollector.mjs +12 -0
  70. package/dist/rules/_sharedValidation.d.mts +87 -0
  71. package/dist/rules/_sharedValidation.d.mts.map +1 -0
  72. package/dist/rules/_sharedValidation.mjs +189 -0
  73. package/package.json +5 -2
  74. package/dist/__tests__/collectors/RequestContextCollector.test.d.mts +0 -2
  75. package/dist/__tests__/collectors/RequestContextCollector.test.d.mts.map +0 -1
  76. package/dist/__tests__/collectors/RequestContextCollector.test.mjs +0 -42
  77. package/dist/collectors/RequestContextCollector.d.mts +0 -5
  78. package/dist/collectors/RequestContextCollector.d.mts.map +0 -1
  79. package/dist/collectors/RequestContextCollector.mjs +0 -15
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=AttrPairNotEqual.test.d.mts.map
@@ -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
+ });
@@ -1,14 +1,14 @@
1
1
  import { describe, expect, it } from "vitest";
2
2
  import { ResourceActionScopeRuleCollector } from "#/rules/ResourceActionScopeRuleCollector.mjs";
3
- const makeContext = (resourceType, action) => ({
4
- payload: {},
3
+ const makeContext = (resourceType, action, scope) => ({
4
+ payload: { ...(scope !== undefined ? { scope } : {}) },
5
5
  resource: { raw: `${resourceType}:1`, resourceType, resourceId: "1" },
6
6
  action,
7
7
  });
8
8
  describe("ResourceActionScopeRuleCollector", () => {
9
9
  const collector = new ResourceActionScopeRuleCollector();
10
10
  it("creates a HasScope rule with action:resourceType", async () => {
11
- const rules = await collector.collect(makeContext("document", "read"));
11
+ const rules = await collector.collect(makeContext("document", "read", "read:document"));
12
12
  expect(rules).toHaveLength(1);
13
13
  expect(rules[0].ruleType).toBe("scope");
14
14
  const attrs = new Map([["scopes", ["read:document"]]]);
@@ -16,12 +16,33 @@ describe("ResourceActionScopeRuleCollector", () => {
16
16
  });
17
17
  it("creates correct scope for nested resource types", async () => {
18
18
  const ctx = {
19
- payload: {},
19
+ payload: { scope: "update:project_member" },
20
20
  resource: { raw: "project:1.member:2", resourceType: "project_member", resourceId: "2" },
21
21
  action: "update",
22
22
  };
23
23
  const rules = await collector.collect(ctx);
24
+ expect(rules).toHaveLength(1);
24
25
  const attrs = new Map([["scopes", ["update:project_member"]]]);
25
26
  expect(rules[0].verify(attrs)).toBe(true);
26
27
  });
28
+ it("returns no rules when payload has no scope claim", async () => {
29
+ const ctx = makeContext("document", "read");
30
+ // payload has no scope — simulates DID JWT
31
+ const rules = await collector.collect(ctx);
32
+ expect(rules).toHaveLength(0);
33
+ });
34
+ it("returns HasScope rule when payload has scope claim", async () => {
35
+ const ctx = {
36
+ payload: { scope: "read:document" },
37
+ resource: { raw: "document:1", resourceType: "document", resourceId: "1" },
38
+ action: "read",
39
+ };
40
+ const rules = await collector.collect(ctx);
41
+ expect(rules).toHaveLength(1);
42
+ expect(rules[0].ruleType).toBe("scope");
43
+ });
44
+ it("still generates HasScope rule when scope is empty string", async () => {
45
+ const rules = await collector.collect(makeContext("document", "read", ""));
46
+ expect(rules).toHaveLength(1);
47
+ });
27
48
  });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=_sharedValidation.test.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_sharedValidation.test.d.mts","sourceRoot":"","sources":["../../../src/__tests__/rules/_sharedValidation.test.mts"],"names":[],"mappings":""}