import { RpcPermission } from './rpc-permission.js' describe('RpcPermission', () => { describe('static', () => { describe('fromString', () => { it('should parse positional scope', () => { const scope = RpcPermission.fromString( 'rpc:com.example.service?aud=did:web:example.com%23service_id', ) expect(scope).not.toBeNull() expect(scope!.aud).toBe('did:web:example.com#service_id') expect(scope!.lxm).toEqual(['com.example.service']) }) it('should parse strings correctly', () => { expect( RpcPermission.fromString('rpc?lxm=com.example.method1&aud=*'), ).toEqual({ aud: '*', lxm: ['com.example.method1'], }) expect( RpcPermission.fromString('rpc:com.example.method1?aud=*'), ).toEqual({ aud: '*', lxm: ['com.example.method1'], }) }) it('should render strings correctly', () => { expect( new RpcPermission('did:web:example.com#service_id', [ 'com.example.service', ]).toString(), ).toBe('rpc:com.example.service?aud=did:web:example.com%23service_id') expect(new RpcPermission('*', ['com.example.method1']).toString()).toBe( 'rpc:com.example.method1?aud=*', ) expect( new RpcPermission('did:web:example.com#service_id', [ 'com.example.method1', 'com.example.method2', ]).toString(), ).toBe( 'rpc?lxm=com.example.method1&lxm=com.example.method2&aud=did:web:example.com%23service_id', ) }) it('should reject scopes without lxm', () => { expect( RpcPermission.fromString('rpc?aud=did:web:example.com%23service_id'), ).toBeNull() expect( RpcPermission.fromString('rpc:?aud=did:web:example.com%23service_id'), ).toBeNull() }) it('should reject scopes without aud', () => { expect( RpcPermission.fromString('rpc?lxm=com.example.method1'), ).toBeNull() expect(RpcPermission.fromString('rpc:com.example.method1')).toBeNull() }) it('should reject scopes with lxm in both positional and query form', () => { expect( RpcPermission.fromString( 'rpc:com.example.method1?aud=did:web:example.com&lxm=com.example.method2', ), ).toBeNull() }) it('should parse valid rpc scope with multiple lxm', () => { const scope = RpcPermission.fromString( 'rpc?aud=*&lxm=com.example.method1&lxm=com.example.method2', ) expect(scope).not.toBeNull() expect(scope!.aud).toBe('*') expect(scope!.lxm).toEqual([ 'com.example.method1', 'com.example.method2', ]) }) it('should reject rpc scope without lxm', () => { const scope = RpcPermission.fromString('rpc?aud=did:web:example.com') expect(scope).toBeNull() }) it('should reject rpc scope without aud', () => { const scope = RpcPermission.fromString('rpc?lxm=com.example.method1') expect(scope).toBeNull() }) it('should reject any aud/any lxm', () => { expect(RpcPermission.fromString('rpc?aud=*&lxm=*')).toBeNull() expect(RpcPermission.fromString('rpc:*?aud=*')).toBeNull() }) it('should reject missing aud', () => { expect(RpcPermission.fromString('rpc:com.example.service')).toBeNull() }) it('should reject invalid aud', () => { expect( RpcPermission.fromString('rpc:com.example.service?aud=invalid'), ).toBeNull() }) it('should reject invalid lxm', () => { expect(RpcPermission.fromString('rpc:invalid')).toBeNull() expect(RpcPermission.fromString('rpc?lxm=invalid')).toBeNull() }) for (const invalid of [ 'rpc:*', 'invalid', 'rpc:invalid', 'rpc:invalid?aud=did:web:example.com', 'rpc:invalid?aud=did:web:example.com%23service_id', 'rpc:foo.bar', 'rpc:com.example.service?aud=did:web:example.com%23service_id&invalid=param', 'rpc:foo.bar.baz?aud=did:web', 'rpc:foo.bar.baz?aud=did:web%23service_id', 'rpc:foo.bar.baz?aud=did:plc:111', 'rpc:foo.bar.baz?aud=did:plc:111%23service_id', 'rpc:foo.bar.baz?aud=did:foo:bar', 'rpc:foo.bar.baz?aud=did:foo:bar%23service_id', 'rpc:foo.bar.baz?aud=did:web:example.com%23service_id&lxm=foo.bar.baz', 'rpc:foo.bar.baz?aud=invalid', 'rpc:foo.bar.baz?aud=invalid', 'rpc:invalid?aud=did:web:example.com', 'rpc:invalid?aud=did:web:example.com%23service_id', 'rpc:com.example.service?aud=invalid', 'notrpc:com.example.service?aud=did:web:example.com%23service_id', 'rpc?lxm=invalid&aud=invalid', ]) { it(`should return null for invalid rpc scope: ${invalid}`, () => { expect(RpcPermission.fromString(invalid)).toBeNull() }) } }) describe('scopeNeededFor', () => { it('should return correct scope string for specific lxm and aud', () => { const scope = RpcPermission.scopeNeededFor({ lxm: 'com.example.service', aud: 'did:web:example.com#service_id', }) expect(scope).toBe( 'rpc:com.example.service?aud=did:web:example.com%23service_id', ) }) it('should return scope that accepts all aud with specific lxm', () => { const scope = RpcPermission.scopeNeededFor({ lxm: 'com.example.method1', aud: '*', }) expect(scope).toBe('rpc:com.example.method1?aud=*') }) }) }) describe('instance', () => { describe('matches', () => { it('should match exact lxm and aud', () => { const scope = RpcPermission.fromString( 'rpc:com.example.service?aud=did:web:example.com%23service_id', ) expect(scope).not.toBeNull() expect( scope!.matches({ lxm: 'com.example.service', aud: 'did:web:example.com#service_id', }), ).toBe(true) }) it('should not match different lxm', () => { const scope = RpcPermission.fromString( 'rpc:com.example.service?aud=did:web:example.com%23service_id', ) expect(scope).not.toBeNull() expect( scope!.matches({ lxm: 'com.example.OtherService', aud: 'did:web:example.com#service_id', }), ).toBe(false) }) it('should not match different aud', () => { const scope = RpcPermission.fromString( 'rpc:com.example.service?aud=did:web:example.com%23service_id', ) expect(scope).not.toBeNull() expect( scope!.matches({ lxm: 'com.example.service', aud: 'did:example:456#service_id', }), ).toBe(false) }) it('should match wildcard aud', () => { const scope = RpcPermission.fromString('rpc:com.example.method1?aud=*') expect(scope).not.toBeNull() expect( scope!.matches({ lxm: 'com.example.method1', aud: 'did:web:example.com#service_id', }), ).toBe(true) }) it('should match wildcard lxm', () => { const scope = RpcPermission.fromString( 'rpc:*?aud=did:web:example.com%23service_id', ) expect(scope).not.toBeNull() expect( scope!.matches({ lxm: 'com.example.method1', aud: 'did:web:example.com#service_id', }), ).toBe(true) }) it('should not match different lxm with wildcard aud', () => { const scope = RpcPermission.fromString( 'rpc:*?aud=did:web:example.com%23service_id', ) expect(scope).not.toBeNull() expect( scope!.matches({ lxm: 'com.example.anyMethod', aud: 'did:web:example.com#service_id', }), ).toBe(true) }) }) describe('toString', () => { it('should format scope with lxm and aud', () => { const scope = new RpcPermission('did:web:example.com#service_id', [ 'com.example.service', ]) expect(scope.toString()).toBe( 'rpc:com.example.service?aud=did:web:example.com%23service_id', ) }) it('should format scope with wildcard aud', () => { const scope = new RpcPermission('*', ['com.example.method1']) expect(scope.toString()).toBe('rpc:com.example.method1?aud=*') }) it('should format scope with wildcard lxm', () => { const scope = new RpcPermission('did:web:example.com#service_id', ['*']) expect(scope.toString()).toBe( 'rpc:*?aud=did:web:example.com%23service_id', ) }) it('simplifies lxm if one of them is "*"', () => { const scope = new RpcPermission('did:web:example.com#service_id', [ '*', 'com.example.method1', ]) expect(scope.toString()).toBe( 'rpc:*?aud=did:web:example.com%23service_id', ) }) }) }) describe('consistency', () => { const testCases: { input: string; expected: string }[] = [ { input: 'rpc:com.example.service?aud=did:web:example.com%23service_id', expected: 'rpc:com.example.service?aud=did:web:example.com%23service_id', }, { input: 'rpc:com.example.service?aud=did:web:example.com#service_id', expected: 'rpc:com.example.service?aud=did:web:example.com%23service_id', }, { input: 'rpc?lxm=com.example.method1&lxm=com.example.method2&aud=*', expected: 'rpc?lxm=com.example.method1&lxm=com.example.method2&aud=*', }, { input: 'rpc?lxm=com.example.method1&lxm=com.example.method2&lxm=*&aud=did:web:example.com%23service_id', expected: 'rpc:*?aud=did:web:example.com%23service_id', }, { input: 'rpc?aud=did:web:example.com%23foo&lxm=com.example.service', expected: 'rpc:com.example.service?aud=did:web:example.com%23foo', }, { input: 'rpc?lxm=com.example.method1&aud=did:web:example.com#foo', expected: 'rpc:com.example.method1?aud=did:web:example.com%23foo', }, { input: 'rpc?lxm=com.example.method1&aud=did:web:example.com%23bar', expected: 'rpc:com.example.method1?aud=did:web:example.com%23bar', }, { input: 'rpc:com.example.method1?&aud=*', expected: 'rpc:com.example.method1?aud=*', }, ] for (const { input, expected } of testCases) { it(`should properly re-format ${input}`, () => { expect(RpcPermission.fromString(input)?.toString()).toBe(expected) }) } }) })