/**
* postgres-mcp - OAuth Scopes Tests
*
* Tests for scope definitions, parsing, matching, and tool group mapping.
*/
import { describe, it, expect } from 'vitest';
import {
SCOPES,
ALL_SCOPES,
TOOL_GROUP_SCOPES,
parseScopes,
hasScope,
hasAnyScope,
hasAllScopes,
getScopeForToolGroup,
hasDatabaseScope,
hasSchemaScope,
hasTableScope,
getScopeDisplayName
} from '../scopes.js';
describe('OAuth Scopes', () => {
describe('SCOPES constant', () => {
it('should define standard scopes', () => {
expect(SCOPES.READ).toBe('read');
expect(SCOPES.WRITE).toBe('write');
expect(SCOPES.ADMIN).toBe('admin');
expect(SCOPES.FULL).toBe('full');
});
});
describe('ALL_SCOPES constant', () => {
it('should include all standard scopes', () => {
expect(ALL_SCOPES).toContain('read');
expect(ALL_SCOPES).toContain('write');
expect(ALL_SCOPES).toContain('admin');
expect(ALL_SCOPES).toContain('full');
});
});
describe('TOOL_GROUP_SCOPES mapping', () => {
it('should map core groups to read scope', () => {
expect(TOOL_GROUP_SCOPES.core).toBe('read');
expect(TOOL_GROUP_SCOPES.jsonb).toBe('read');
expect(TOOL_GROUP_SCOPES.text).toBe('read');
expect(TOOL_GROUP_SCOPES.stats).toBe('read');
});
it('should map write operations to write scope', () => {
expect(TOOL_GROUP_SCOPES.transactions).toBe('write');
});
it('should map administrative groups to admin scope', () => {
expect(TOOL_GROUP_SCOPES.admin).toBe('admin');
expect(TOOL_GROUP_SCOPES.backup).toBe('admin');
expect(TOOL_GROUP_SCOPES.partitioning).toBe('admin');
expect(TOOL_GROUP_SCOPES.cron).toBe('admin');
expect(TOOL_GROUP_SCOPES.partman).toBe('admin');
});
it('should map extension groups appropriately', () => {
expect(TOOL_GROUP_SCOPES.vector).toBe('read');
expect(TOOL_GROUP_SCOPES.postgis).toBe('read');
expect(TOOL_GROUP_SCOPES.citext).toBe('read');
expect(TOOL_GROUP_SCOPES.ltree).toBe('read');
});
});
describe('parseScopes', () => {
it('should parse space-delimited scope string', () => {
const scopes = parseScopes('read write admin');
expect(scopes).toEqual(['read', 'write', 'admin']);
});
it('should handle single scope', () => {
const scopes = parseScopes('read');
expect(scopes).toEqual(['read']);
});
it('should handle empty string', () => {
const scopes = parseScopes('');
expect(scopes).toEqual([]);
});
it('should handle undefined', () => {
const scopes = parseScopes(undefined);
expect(scopes).toEqual([]);
});
it('should filter out empty strings from extra spaces', () => {
const scopes = parseScopes('read write admin');
expect(scopes).toEqual(['read', 'write', 'admin']);
});
it('should handle pattern scopes', () => {
const scopes = parseScopes('read db:mydb table:public:users');
expect(scopes).toEqual(['read', 'db:mydb', 'table:public:users']);
});
});
describe('hasScope', () => {
it('should return true for direct match', () => {
expect(hasScope(['read', 'write'], 'read')).toBe(true);
expect(hasScope(['read', 'write'], 'write')).toBe(true);
});
it('should return false when scope not present', () => {
expect(hasScope(['read'], 'write')).toBe(false);
expect(hasScope(['read'], 'admin')).toBe(false);
});
it('should grant full access from full scope', () => {
expect(hasScope(['full'], 'read')).toBe(true);
expect(hasScope(['full'], 'write')).toBe(true);
expect(hasScope(['full'], 'admin')).toBe(true);
expect(hasScope(['full'], 'anything')).toBe(true);
});
it('should grant read and write access from admin scope', () => {
expect(hasScope(['admin'], 'read')).toBe(true);
expect(hasScope(['admin'], 'write')).toBe(true);
});
it('should grant read access from write scope', () => {
expect(hasScope(['write'], 'read')).toBe(true);
expect(hasScope(['write'], 'admin')).toBe(false);
});
it('should not grant admin access from read/write', () => {
expect(hasScope(['read'], 'admin')).toBe(false);
expect(hasScope(['write'], 'admin')).toBe(false);
});
it('should handle empty scopes array', () => {
expect(hasScope([], 'read')).toBe(false);
});
});
describe('hasAnyScope', () => {
it('should return true if any scope matches', () => {
expect(hasAnyScope(['read'], ['read', 'write'])).toBe(true);
expect(hasAnyScope(['write'], ['read', 'write'])).toBe(true);
});
it('should return false if no scope matches', () => {
expect(hasAnyScope(['read'], ['admin', 'write'])).toBe(false);
});
it('should consider scope hierarchy', () => {
expect(hasAnyScope(['full'], ['admin'])).toBe(true);
expect(hasAnyScope(['admin'], ['read', 'write'])).toBe(true);
});
it('should handle empty required scopes', () => {
expect(hasAnyScope(['read'], [])).toBe(false);
});
});
describe('hasAllScopes', () => {
it('should return true if all scopes match', () => {
expect(hasAllScopes(['read', 'write'], ['read', 'write'])).toBe(true);
});
it('should return false if any scope missing', () => {
expect(hasAllScopes(['read'], ['read', 'write'])).toBe(false);
});
it('should consider scope hierarchy', () => {
// Full grants everything
expect(hasAllScopes(['full'], ['read', 'write', 'admin'])).toBe(true);
// Admin grants read and write
expect(hasAllScopes(['admin'], ['read', 'write'])).toBe(true);
});
it('should handle empty required scopes', () => {
expect(hasAllScopes(['read'], [])).toBe(true);
});
});
describe('getScopeForToolGroup', () => {
it('should return correct scope for known groups', () => {
expect(getScopeForToolGroup('core')).toBe('read');
expect(getScopeForToolGroup('transactions')).toBe('write');
expect(getScopeForToolGroup('admin')).toBe('admin');
});
it('should default to read for unknown groups', () => {
// Type assertion needed for testing unknown group
expect(getScopeForToolGroup('unknown' as never)).toBe('read');
});
});
describe('hasDatabaseScope', () => {
it('should match database pattern scope', () => {
expect(hasDatabaseScope(['db:mydb'], 'mydb')).toBe(true);
expect(hasDatabaseScope(['db:mydb'], 'otherdb')).toBe(false);
});
it('should grant access with full scope', () => {
expect(hasDatabaseScope(['full'], 'anydb')).toBe(true);
});
it('should grant access with admin scope', () => {
expect(hasDatabaseScope(['admin'], 'anydb')).toBe(true);
});
it('should not match without pattern scope', () => {
expect(hasDatabaseScope(['read'], 'mydb')).toBe(false);
});
});
describe('hasSchemaScope', () => {
it('should match schema pattern scope', () => {
expect(hasSchemaScope(['schema:public'], 'public')).toBe(true);
expect(hasSchemaScope(['schema:public'], 'private')).toBe(false);
});
it('should grant access with full scope', () => {
expect(hasSchemaScope(['full'], 'anyschema')).toBe(true);
});
it('should grant access with admin scope', () => {
expect(hasSchemaScope(['admin'], 'anyschema')).toBe(true);
});
});
describe('hasTableScope', () => {
it('should match table pattern scope', () => {
expect(hasTableScope(['table:public:users'], 'public', 'users')).toBe(true);
expect(hasTableScope(['table:public:users'], 'public', 'orders')).toBe(false);
});
it('should inherit from schema scope', () => {
expect(hasTableScope(['schema:public'], 'public', 'users')).toBe(true);
expect(hasTableScope(['schema:public'], 'private', 'users')).toBe(false);
});
it('should grant access with full scope', () => {
expect(hasTableScope(['full'], 'any', 'table')).toBe(true);
});
it('should grant access with admin scope', () => {
expect(hasTableScope(['admin'], 'any', 'table')).toBe(true);
});
});
describe('getScopeDisplayName', () => {
it('should return friendly names for standard scopes', () => {
expect(getScopeDisplayName('read')).toBe('Read Only');
expect(getScopeDisplayName('write')).toBe('Read/Write');
expect(getScopeDisplayName('admin')).toBe('Administrative');
expect(getScopeDisplayName('full')).toBe('Full Access');
});
it('should format database scope', () => {
expect(getScopeDisplayName('db:mydb')).toBe('Database: mydb');
});
it('should format schema scope', () => {
expect(getScopeDisplayName('schema:public')).toBe('Schema: public');
});
it('should format table scope', () => {
expect(getScopeDisplayName('table:public:users')).toBe('Table: public:users');
});
it('should return unknown scopes unchanged', () => {
expect(getScopeDisplayName('custom:scope')).toBe('custom:scope');
});
});
});