109 lines
3.7 KiB
TypeScript
109 lines
3.7 KiB
TypeScript
/**
|
|
* Unit tests for scope enforcement and auth in PlaywrightAgent.
|
|
* These tests use the private helper methods indirectly via the agent's behavior
|
|
* by testing the isExcludedPath, isExternalLink, and isAllowedUrl logic
|
|
* through subclassing or direct method exposure.
|
|
*
|
|
* Since these methods are private, we test observable behavior via
|
|
* discoverActions and executeAction with mock pages or just verify config logic.
|
|
*/
|
|
|
|
import { ExplorationConfig, DEFAULT_EXPLORATION_CONFIG } from '../../src/core/ExplorationConfig';
|
|
|
|
describe('ExplorationConfig', () => {
|
|
it('has sensible defaults', () => {
|
|
const cfg = { ...DEFAULT_EXPLORATION_CONFIG };
|
|
expect(cfg.maxStates).toBe(50);
|
|
expect(cfg.maxDepth).toBe(5);
|
|
expect(cfg.actionDelayMs).toBe(500);
|
|
expect(cfg.sessionTimeoutMs).toBe(300000);
|
|
expect(cfg.fuzzingEnabled).toBe(true);
|
|
expect(cfg.fuzzingIntensity).toBe('medium');
|
|
expect(cfg.auth).toBeNull();
|
|
expect(cfg.excludedPaths).toEqual([]);
|
|
expect(cfg.excludedSelectors).toEqual([]);
|
|
});
|
|
|
|
it('accepts cookies auth config', () => {
|
|
const config: ExplorationConfig = {
|
|
...DEFAULT_EXPLORATION_CONFIG,
|
|
auth: {
|
|
type: 'cookies',
|
|
cookies: [{ name: 'session', value: 'abc', domain: 'localhost' }],
|
|
},
|
|
};
|
|
expect(config.auth?.type).toBe('cookies');
|
|
});
|
|
|
|
it('accepts headers auth config', () => {
|
|
const config: ExplorationConfig = {
|
|
...DEFAULT_EXPLORATION_CONFIG,
|
|
auth: {
|
|
type: 'headers',
|
|
headers: { Authorization: 'Bearer token123' },
|
|
},
|
|
};
|
|
expect(config.auth?.type).toBe('headers');
|
|
});
|
|
|
|
it('accepts login_flow auth config', () => {
|
|
const config: ExplorationConfig = {
|
|
...DEFAULT_EXPLORATION_CONFIG,
|
|
auth: {
|
|
type: 'login_flow',
|
|
loginUrl: 'http://app.com/login',
|
|
usernameSelector: 'input[name="email"]',
|
|
passwordSelector: 'input[name="password"]',
|
|
submitSelector: 'button[type="submit"]',
|
|
username: 'user@test.com',
|
|
password: 'secret',
|
|
},
|
|
};
|
|
expect(config.auth?.type).toBe('login_flow');
|
|
});
|
|
});
|
|
|
|
// Helper to test URL-based scope rules (extracted for testability)
|
|
describe('Scope URL rules', () => {
|
|
function isExcludedPath(urlOrPath: string, excludedPaths: string[]): boolean {
|
|
if (excludedPaths.length === 0) return false;
|
|
try {
|
|
const parsed = new URL(urlOrPath, 'http://placeholder');
|
|
return excludedPaths.some((p) => parsed.pathname.startsWith(p));
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function isExternalLink(href: string, currentUrl: string, allowedDomains: string[]): boolean {
|
|
if (allowedDomains.length === 0) return false;
|
|
try {
|
|
const base = new URL(currentUrl);
|
|
const target = new URL(href, base.origin);
|
|
return !allowedDomains.includes(target.hostname);
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
it('excludes paths correctly', () => {
|
|
expect(isExcludedPath('http://app.com/logout', ['/logout'])).toBe(true);
|
|
expect(isExcludedPath('http://app.com/home', ['/logout'])).toBe(false);
|
|
expect(isExcludedPath('http://app.com/admin/users', ['/admin'])).toBe(true);
|
|
});
|
|
|
|
it('allows paths when no exclusions', () => {
|
|
expect(isExcludedPath('http://app.com/logout', [])).toBe(false);
|
|
});
|
|
|
|
it('detects external links', () => {
|
|
expect(isExternalLink('http://external.com/page', 'http://myapp.com', ['myapp.com'])).toBe(true);
|
|
expect(isExternalLink('/page', 'http://myapp.com', ['myapp.com'])).toBe(false);
|
|
expect(isExternalLink('http://myapp.com/page', 'http://myapp.com', ['myapp.com'])).toBe(false);
|
|
});
|
|
|
|
it('allows all links when no allowedDomains', () => {
|
|
expect(isExternalLink('http://external.com/page', 'http://myapp.com', [])).toBe(false);
|
|
});
|
|
});
|