1import { describe, expect, it } from "vite-plus/test";
2import * as Schema from "effect/Schema";
3
4import {
5 encodeOAuthScope,
6 OAuthScopeEncodingError,
7 parseAllowedOAuthScope,
8 parseOAuthScope,
9} from "./oauthScope.ts";
10
11const isOAuthScopeEncodingError = Schema.is(OAuthScopeEncodingError);
12
13describe("OAuth scopes", () => {
14 it("parses an RFC 6749 space-delimited scope set without duplicating permissions", () => {
15 expect(parseOAuthScope("orchestration:read access:write orchestration:read")).toEqual([
16 "orchestration:read",
17 "access:write",
18 ]);
19 });
20
21 it("rejects whitespace that is not the SP delimiter or introduces empty tokens", () => {
22 expect(parseOAuthScope("orchestration:read\taccess:write")).toBeNull();
23 expect(parseOAuthScope("orchestration:read access:write")).toBeNull();
24 });
25
26 it("encodes and restricts requested scopes to the allowed capability set", () => {
27 expect(encodeOAuthScope(["orchestration:read", "access:write"])).toBe(
28 "orchestration:read access:write",
29 );
30 expect(
31 parseAllowedOAuthScope({
32 value: "orchestration:read access:write",
33 allowedScopes: new Set(["orchestration:read", "access:write"] as const),
34 }),
35 ).toEqual(["orchestration:read", "access:write"]);
36 expect(
37 parseAllowedOAuthScope({
38 value: "orchestration:read relay:write",
39 allowedScopes: new Set(["orchestration:read", "access:write"] as const),
40 }),
41 ).toBeNull();
42 });
43
44 it("reports invalid encoding input structurally", () => {
45 expect.assertions(5);
46
47 try {
48 encodeOAuthScope(["access:read", "invalid scope", "access:read"]);
49 } catch (error) {
50 expect(error).toBeInstanceOf(OAuthScopeEncodingError);
51 if (!isOAuthScopeEncodingError(error)) return;
52
53 expect(error.scopes).toEqual(["access:read", "invalid scope", "access:read"]);
54 expect(error.invalidScopes).toEqual(["invalid scope"]);
55 expect(error.duplicateScopes).toEqual(["access:read"]);
56 expect(error.message).toBe(
57 "OAuth scopes must be non-empty, syntactically valid, and unique.",
58 );
59 }
60 });
61});