import { describe, expect, it } from 'bun:test';
import { DwnErrorCode } from '../../src/core/dwn-error.js';
import { RecordsGrantAuthorization } from '../../src/core/records-grant-authorization.js';
import { DwnInterfaceName, DwnMethodName } from '../../src/enums/dwn-interface-method.js';
describe('RecordsGrantAuthorization', () => {
    const grantor = 'did:example:alice';
    const grantee = 'did:example:bob';
    const protocol = 'https://example.com/protocol';
    const timestamp = '2026-05-31T00:00:00.000000Z';
    const validationStateReader = {
        fetchOldestGrantRevocation: async () => undefined,
    };
    function makeRecordsWrite(contextId) {
        const contextIdSegments = contextId.split('/');
        return {
            recordId: contextIdSegments[contextIdSegments.length - 1] ?? contextId,
            contextId,
            descriptor: {
                interface: DwnInterfaceName.Records,
                method: DwnMethodName.Write,
                protocol: 'https://example.com/protocol',
                protocolPath: 'thread/message',
                dataFormat: 'application/json',
                dataCid: 'bafybeigdyrztj3md',
                dataSize: 2,
                dateCreated: '2026-01-01T00:00:00.000000Z',
                messageTimestamp: '2026-01-01T00:00:00.000000Z',
            },
        };
    }
    function makePermissionGrant(method, scope) {
        return {
            id: 'grant-record-id',
            grantor,
            grantee,
            dateGranted: '2026-01-01T00:00:00.000000Z',
            dateExpires: '2027-01-01T00:00:00.000000Z',
            scope: {
                interface: DwnInterfaceName.Records,
                method,
                ...scope,
            },
        };
    }
    function makeIncomingMessage(method, filter) {
        return {
            descriptor: {
                interface: DwnInterfaceName.Records,
                method,
                messageTimestamp: timestamp,
                filter,
            },
        };
    }
    async function expectQueryOrSubscribeScopeMismatch(method, grantScope, filter) {
        await expect(RecordsGrantAuthorization.authorizeQueryOrSubscribe({
            incomingMessage: makeIncomingMessage(method, filter),
            expectedGrantor: grantor,
            expectedGrantee: grantee,
            permissionGrant: makePermissionGrant(DwnMethodName.Read, grantScope),
            validationStateReader,
        })).rejects.toThrow(DwnErrorCode.RecordsGrantAuthorizationQueryOrSubscribeProtocolScopeMismatch);
    }
    it('matches contextId scopes by context subtree', () => {
        const scope = {
            interface: DwnInterfaceName.Records,
            method: DwnMethodName.Read,
            protocol: 'https://example.com/protocol',
            contextId: 'root',
        };
        expect(() => {
            RecordsGrantAuthorization.verifyScope(makeRecordsWrite('root'), scope);
        }).not.toThrow();
        expect(() => {
            RecordsGrantAuthorization.verifyScope(makeRecordsWrite('root/child'), scope);
        }).not.toThrow();
        expect(() => {
            RecordsGrantAuthorization.verifyScope(makeRecordsWrite('rootEVIL'), scope);
        }).toThrow(DwnErrorCode.RecordsGrantAuthorizationScopeContextIdMismatch);
    });
    describe('authorizeQueryOrSubscribe()', () => {
        it('rejects a broad query with a protocolPath-scoped grant', async () => {
            await expectQueryOrSubscribeScopeMismatch(DwnMethodName.Query, { protocol, protocolPath: 'thread/message' }, { protocol });
        });
        it('allows a matching protocolPath-scoped query', async () => {
            await expect(RecordsGrantAuthorization.authorizeQueryOrSubscribe({
                incomingMessage: makeIncomingMessage(DwnMethodName.Query, {
                    protocol,
                    protocolPath: 'thread/message',
                }),
                expectedGrantor: grantor,
                expectedGrantee: grantee,
                permissionGrant: makePermissionGrant(DwnMethodName.Read, {
                    protocol,
                    protocolPath: 'thread/message',
                }),
                validationStateReader,
            })).resolves.toBeUndefined();
        });
        it('rejects sibling protocolPath and contextId filters', async () => {
            await expectQueryOrSubscribeScopeMismatch(DwnMethodName.Query, { protocol, protocolPath: 'thread/message' }, { protocol, protocolPath: 'thread/reaction' });
            await expectQueryOrSubscribeScopeMismatch(DwnMethodName.Subscribe, { protocol, contextId: 'root' }, { protocol, contextId: 'rootEVIL' });
        });
        it('allows protocol-scoped query, subscribe, and count filters with a Records.Read grant', async () => {
            for (const method of [DwnMethodName.Query, DwnMethodName.Subscribe, DwnMethodName.Count]) {
                await expect(RecordsGrantAuthorization.authorizeQueryOrSubscribe({
                    incomingMessage: makeIncomingMessage(method, { protocol }),
                    expectedGrantor: grantor,
                    expectedGrantee: grantee,
                    permissionGrant: makePermissionGrant(DwnMethodName.Read, { protocol }),
                    validationStateReader,
                })).resolves.toBeUndefined();
            }
        });
        it('rejects read-like Records grants that do not use method Read', async () => {
            const cases = [
                [DwnMethodName.Query, DwnMethodName.Query],
                [DwnMethodName.Subscribe, DwnMethodName.Subscribe],
                [DwnMethodName.Count, DwnMethodName.Count],
                [DwnMethodName.Write, DwnMethodName.Query],
            ];
            for (const [grantMethod, incomingMethod] of cases) {
                await expect(RecordsGrantAuthorization.authorizeQueryOrSubscribe({
                    incomingMessage: makeIncomingMessage(incomingMethod, { protocol }),
                    expectedGrantor: grantor,
                    expectedGrantee: grantee,
                    permissionGrant: makePermissionGrant(grantMethod, { protocol }),
                    validationStateReader,
                })).rejects.toThrow(DwnErrorCode.GrantAuthorizationMethodMismatch);
            }
        });
    });
});
//# sourceMappingURL=records-grant-authorization.spec.js.map