mirror of
https://github.com/yaklang/yaklang-chrome-extension.git
synced 2026-09-24 20:21:53 +08:00
feat: advance browser agent integration workflows
This commit is contained in:
@@ -0,0 +1,262 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import type { BrowserAuthorizationWorkspace } from '../engine';
|
||||
import {
|
||||
authorizationWorkspaceUIReducer,
|
||||
authorizationWorkspaceStage,
|
||||
INITIAL_AUTHORIZATION_WORKSPACE_UI,
|
||||
normalizePersistedAuthorizationWorkspaceUI,
|
||||
persistedAuthorizationWorkspaceUI,
|
||||
} from './workspace-reducer';
|
||||
|
||||
function fixtureWorkspace(): BrowserAuthorizationWorkspace {
|
||||
return {
|
||||
version: 1,
|
||||
id: 'workspace-1',
|
||||
engineInstanceId: 'engine-1',
|
||||
mode: 'horizontal',
|
||||
state: 'ready',
|
||||
left: {
|
||||
accountLabel: '账号 A',
|
||||
origin: 'https://example.test',
|
||||
target: { tabId: 11, frameId: 0, documentId: 'document-a' },
|
||||
authentication: { status: 'authenticated', cookieCount: 1, storageEntryCount: 0 },
|
||||
},
|
||||
right: {
|
||||
accountLabel: '账号 B',
|
||||
origin: 'https://example.test',
|
||||
target: { tabId: 22, frameId: 0, documentId: 'document-b' },
|
||||
authentication: { status: 'authenticated', cookieCount: 1, storageEntryCount: 0 },
|
||||
},
|
||||
proof: {
|
||||
level: 'strong',
|
||||
sameOrigin: true,
|
||||
cookieStoreRelation: 'different',
|
||||
accountEvidenceRelation: 'different',
|
||||
requestCredentialRelation: 'different',
|
||||
refreshCheck: 'passed',
|
||||
reasons: ['隔离成立'],
|
||||
},
|
||||
baselines: {},
|
||||
baselinePair: {
|
||||
state: 'waiting',
|
||||
reasons: ['等待正常请求'],
|
||||
resourceCandidates: [],
|
||||
operationCandidates: [],
|
||||
},
|
||||
expiresAt: Date.now() + 60_000,
|
||||
};
|
||||
}
|
||||
|
||||
describe('authorization workspace UI reducer', () => {
|
||||
it('initializes a renewed workspace and clears evidence tied to the old document', () => {
|
||||
const workspace = { id: 'renewed' } as BrowserAuthorizationWorkspace;
|
||||
const previous = {
|
||||
...INITIAL_AUTHORIZATION_WORKSPACE_UI,
|
||||
candidates: { left: [{ id: 'old-left' }], right: [{ id: 'old-right' }] } as never,
|
||||
selected: { left: 'old-left', right: 'old-right' },
|
||||
selectedPlanCandidateId: 'old-plan',
|
||||
};
|
||||
|
||||
const next = authorizationWorkspaceUIReducer(previous, {
|
||||
type: 'workspace.initialize',
|
||||
workspace,
|
||||
});
|
||||
|
||||
expect(next.workspace).toBe(workspace);
|
||||
expect(next.candidates).toEqual({ left: [], right: [] });
|
||||
expect(next.selected).toEqual({ left: '', right: '' });
|
||||
expect(next.selectedPlanCandidateId).toBe('');
|
||||
});
|
||||
|
||||
it('resets workflow evidence without discarding the selected identities', () => {
|
||||
const previous = {
|
||||
...INITIAL_AUTHORIZATION_WORKSPACE_UI,
|
||||
leftTabId: 11,
|
||||
rightTabId: 12,
|
||||
workspace: { id: 'old' } as BrowserAuthorizationWorkspace,
|
||||
capture: { left: { active: true } } as never,
|
||||
};
|
||||
const next = authorizationWorkspaceUIReducer(previous, { type: 'workspace.reset' });
|
||||
|
||||
expect(next.leftTabId).toBe(11);
|
||||
expect(next.rightTabId).toBe(12);
|
||||
expect(next.workspace).toBeUndefined();
|
||||
expect(next.capture).toEqual({});
|
||||
});
|
||||
|
||||
it('persists only durable workflow state', () => {
|
||||
const value = persistedAuthorizationWorkspaceUI({
|
||||
...INITIAL_AUTHORIZATION_WORKSPACE_UI,
|
||||
inspection: { version: 1 } as never,
|
||||
capture: { left: { active: true } } as never,
|
||||
});
|
||||
expect(value).not.toHaveProperty('inspection');
|
||||
expect(value).not.toHaveProperty('capture');
|
||||
});
|
||||
|
||||
it('fails closed when a restarted UI session contains a malformed workspace', () => {
|
||||
const next = authorizationWorkspaceUIReducer(INITIAL_AUTHORIZATION_WORKSPACE_UI, {
|
||||
type: 'hydrate',
|
||||
value: {
|
||||
mode: 'vertical',
|
||||
leftTabId: 11,
|
||||
rightTabId: 'not-a-tab',
|
||||
leftLabel: '低权限账号',
|
||||
workspace: { id: 'truncated-before-storage-write' },
|
||||
candidates: { left: [null], right: { invalid: true } },
|
||||
selected: null,
|
||||
},
|
||||
});
|
||||
|
||||
expect(next).toMatchObject({
|
||||
mode: 'vertical',
|
||||
leftTabId: 11,
|
||||
leftLabel: '低权限账号',
|
||||
workspace: undefined,
|
||||
candidates: { left: [], right: [] },
|
||||
selected: { left: '', right: '' },
|
||||
});
|
||||
expect(next.rightTabId).toBeUndefined();
|
||||
});
|
||||
|
||||
it('normalizes a valid persisted workflow but drops invalid candidate entries', () => {
|
||||
const workspace = {
|
||||
...fixtureWorkspace(),
|
||||
createdAt: Date.now(),
|
||||
};
|
||||
const normalized = normalizePersistedAuthorizationWorkspaceUI({
|
||||
mode: 'horizontal',
|
||||
leftTabId: 11,
|
||||
rightTabId: 22,
|
||||
leftLabel: '账号 A',
|
||||
rightLabel: '账号 B',
|
||||
workspace,
|
||||
candidates: {
|
||||
left: [{
|
||||
id: 'left-request',
|
||||
method: 'GET',
|
||||
url: 'https://example.test/api/profile?id=1',
|
||||
path: '/api/profile',
|
||||
resourceType: 'xmlhttprequest',
|
||||
startedAt: Date.now(),
|
||||
eligible: true,
|
||||
reasons: [],
|
||||
}, { id: 'invalid-url', url: 'javascript:alert(1)' }],
|
||||
right: [],
|
||||
},
|
||||
selected: { left: 'left-request', right: '' },
|
||||
selectedPlanCandidateId: '',
|
||||
canaryPaths: 'data.owner.id',
|
||||
});
|
||||
|
||||
expect(normalized?.workspace?.id).toBe('workspace-1');
|
||||
expect(normalized?.candidates?.left).toEqual([
|
||||
expect.objectContaining({ id: 'left-request' }),
|
||||
]);
|
||||
expect(normalized?.selected?.left).toBe('left-request');
|
||||
});
|
||||
|
||||
it('models the complete identity-to-evidence workflow without losing capture state', () => {
|
||||
let current = INITIAL_AUTHORIZATION_WORKSPACE_UI;
|
||||
expect(authorizationWorkspaceStage(current)).toBe('identity');
|
||||
const initial = fixtureWorkspace();
|
||||
current = authorizationWorkspaceUIReducer(current, {
|
||||
type: 'workspace.initialize',
|
||||
workspace: initial,
|
||||
});
|
||||
current = authorizationWorkspaceUIReducer(current, {
|
||||
type: 'capture.replace',
|
||||
capture: {
|
||||
left: { active: true, count: 1 } as never,
|
||||
right: { active: true, count: 1 } as never,
|
||||
},
|
||||
});
|
||||
expect(authorizationWorkspaceStage(current)).toBe('normal-requests');
|
||||
|
||||
const baseline = {
|
||||
id: 'baseline',
|
||||
networkRequestId: 'request',
|
||||
request: {
|
||||
method: 'GET',
|
||||
url: 'https://example.test/api/profile?id=1',
|
||||
path: '/api/profile',
|
||||
contentType: 'application/json',
|
||||
actionFingerprint: 'fingerprint',
|
||||
},
|
||||
};
|
||||
const bound = {
|
||||
...initial,
|
||||
baselines: { left: { ...baseline, id: 'left' }, right: { ...baseline, id: 'right' } },
|
||||
baselinePair: {
|
||||
state: 'matched' as const,
|
||||
reasons: ['同类请求'],
|
||||
resourceCandidates: [{
|
||||
id: 'resource-id',
|
||||
source: 'wire' as const,
|
||||
location: 'query' as const,
|
||||
path: 'query.id',
|
||||
category: 'identifier',
|
||||
confidence: 'high' as const,
|
||||
requiresLogicalBinding: false,
|
||||
reasons: ['A/B 值不同'],
|
||||
}],
|
||||
operationCandidates: [],
|
||||
},
|
||||
};
|
||||
current = authorizationWorkspaceUIReducer(current, {
|
||||
type: 'baselines.loaded',
|
||||
candidates: {
|
||||
left: [{ id: 'left-request' }] as never,
|
||||
right: [{ id: 'right-request' }] as never,
|
||||
},
|
||||
selected: { left: 'left-request', right: 'right-request' },
|
||||
});
|
||||
current = authorizationWorkspaceUIReducer(current, {
|
||||
type: 'baselines.bound',
|
||||
workspace: bound,
|
||||
selectedPlanCandidateId: 'resource-id',
|
||||
});
|
||||
expect(authorizationWorkspaceStage(current)).toBe('plan');
|
||||
|
||||
const planned = {
|
||||
...bound,
|
||||
plan: {
|
||||
id: 'plan-1',
|
||||
mode: 'horizontal' as const,
|
||||
candidateId: 'resource-id',
|
||||
state: 'ready' as const,
|
||||
selector: { source: 'wire' as const, location: 'query' as const, path: 'query.id' },
|
||||
cases: [],
|
||||
requestBudget: 4,
|
||||
requiresDynamicRebuild: false,
|
||||
reasons: ['固定四项矩阵'],
|
||||
},
|
||||
};
|
||||
current = authorizationWorkspaceUIReducer(current, {
|
||||
type: 'workspace.updated',
|
||||
workspace: planned,
|
||||
});
|
||||
expect(authorizationWorkspaceStage(current)).toBe('execution');
|
||||
|
||||
current = authorizationWorkspaceUIReducer(current, {
|
||||
type: 'workspace.updated',
|
||||
workspace: {
|
||||
...planned,
|
||||
execution: {
|
||||
id: 'execution-1',
|
||||
state: 'completed',
|
||||
verdict: 'protected',
|
||||
confidence: 'high',
|
||||
requestCount: 4,
|
||||
cases: [],
|
||||
evidence: [],
|
||||
evidenceAvailable: true,
|
||||
reasons: ['交叉访问均被拒绝'],
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(authorizationWorkspaceStage(current)).toBe('evidence');
|
||||
expect(current.capture.left?.active).toBe(true);
|
||||
expect(persistedAuthorizationWorkspaceUI(current)).not.toHaveProperty('capture');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user