Initial import from garrytan/gstack@026751e (main snapshot via local relay)
Some checks failed
Workflow Lint / actionlint (push) Has been cancelled
Build CI Image / build (push) Has been cancelled
Skill Docs Freshness / check-freshness (push) Has been cancelled
Periodic Evals / build-image (push) Has been cancelled
Periodic Evals / evals (map[file:test/codex-e2e.test.ts name:e2e-codex]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/gemini-e2e.test.ts name:e2e-gemini]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-design.test.ts name:e2e-design]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-plan.test.ts name:e2e-plan]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-qa-bugs.test.ts name:e2e-qa-bugs]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-qa-workflow.test.ts name:e2e-qa-workflow]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-review.test.ts name:e2e-review]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-workflow.test.ts name:e2e-workflow]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-routing-e2e.test.ts name:e2e-routing]) (push) Has been cancelled
Some checks failed
Workflow Lint / actionlint (push) Has been cancelled
Build CI Image / build (push) Has been cancelled
Skill Docs Freshness / check-freshness (push) Has been cancelled
Periodic Evals / build-image (push) Has been cancelled
Periodic Evals / evals (map[file:test/codex-e2e.test.ts name:e2e-codex]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/gemini-e2e.test.ts name:e2e-gemini]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-design.test.ts name:e2e-design]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-plan.test.ts name:e2e-plan]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-qa-bugs.test.ts name:e2e-qa-bugs]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-qa-workflow.test.ts name:e2e-qa-workflow]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-review.test.ts name:e2e-review]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-workflow.test.ts name:e2e-workflow]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-routing-e2e.test.ts name:e2e-routing]) (push) Has been cancelled
Source: https://github.com/garrytan/gstack/commit/026751e
This commit is contained in:
120
browse/test/activity.test.ts
Normal file
120
browse/test/activity.test.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
import { describe, it, expect } from 'bun:test';
|
||||
import { filterArgs, emitActivity, getActivityAfter, getActivityHistory, subscribe } from '../src/activity';
|
||||
|
||||
describe('filterArgs — privacy filtering', () => {
|
||||
it('redacts fill value for password fields', () => {
|
||||
expect(filterArgs('fill', ['#password', 'mysecret123'])).toEqual(['#password', '[REDACTED]']);
|
||||
expect(filterArgs('fill', ['input[type=passwd]', 'abc'])).toEqual(['input[type=passwd]', '[REDACTED]']);
|
||||
});
|
||||
|
||||
it('preserves fill value for non-password fields', () => {
|
||||
expect(filterArgs('fill', ['#email', 'user@test.com'])).toEqual(['#email', 'user@test.com']);
|
||||
});
|
||||
|
||||
it('redacts type command args', () => {
|
||||
expect(filterArgs('type', ['my password'])).toEqual(['[REDACTED]']);
|
||||
});
|
||||
|
||||
it('redacts Authorization header', () => {
|
||||
expect(filterArgs('header', ['Authorization:Bearer abc123'])).toEqual(['Authorization:[REDACTED]']);
|
||||
});
|
||||
|
||||
it('preserves non-sensitive headers', () => {
|
||||
expect(filterArgs('header', ['Content-Type:application/json'])).toEqual(['Content-Type:application/json']);
|
||||
});
|
||||
|
||||
it('redacts cookie values', () => {
|
||||
expect(filterArgs('cookie', ['session_id=abc123'])).toEqual(['session_id=[REDACTED]']);
|
||||
});
|
||||
|
||||
it('redacts sensitive URL query params', () => {
|
||||
const result = filterArgs('goto', ['https://example.com?api_key=secret&page=1']);
|
||||
expect(result[0]).toContain('api_key=%5BREDACTED%5D');
|
||||
expect(result[0]).toContain('page=1');
|
||||
});
|
||||
|
||||
it('preserves non-sensitive URL query params', () => {
|
||||
const result = filterArgs('goto', ['https://example.com?page=1&sort=name']);
|
||||
expect(result[0]).toBe('https://example.com?page=1&sort=name');
|
||||
});
|
||||
|
||||
it('handles empty args', () => {
|
||||
expect(filterArgs('click', [])).toEqual([]);
|
||||
});
|
||||
|
||||
it('handles non-URL non-sensitive args', () => {
|
||||
expect(filterArgs('click', ['@e3'])).toEqual(['@e3']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('emitActivity', () => {
|
||||
it('emits with auto-incremented id', () => {
|
||||
const e1 = emitActivity({ type: 'command_start', command: 'goto', args: ['https://example.com'] });
|
||||
const e2 = emitActivity({ type: 'command_end', command: 'goto', status: 'ok', duration: 100 });
|
||||
expect(e2.id).toBe(e1.id + 1);
|
||||
});
|
||||
|
||||
it('truncates long results', () => {
|
||||
const longResult = 'x'.repeat(500);
|
||||
const entry = emitActivity({ type: 'command_end', command: 'text', result: longResult });
|
||||
expect(entry.result!.length).toBeLessThanOrEqual(203); // 200 + "..."
|
||||
});
|
||||
|
||||
it('applies privacy filtering', () => {
|
||||
const entry = emitActivity({ type: 'command_start', command: 'type', args: ['my secret password'] });
|
||||
expect(entry.args).toEqual(['[REDACTED]']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getActivityAfter', () => {
|
||||
it('returns entries after cursor', () => {
|
||||
const e1 = emitActivity({ type: 'command_start', command: 'test1' });
|
||||
const e2 = emitActivity({ type: 'command_start', command: 'test2' });
|
||||
const result = getActivityAfter(e1.id);
|
||||
expect(result.entries.some(e => e.id === e2.id)).toBe(true);
|
||||
expect(result.gap).toBe(false);
|
||||
});
|
||||
|
||||
it('returns all entries when cursor is 0', () => {
|
||||
emitActivity({ type: 'command_start', command: 'test3' });
|
||||
const result = getActivityAfter(0);
|
||||
expect(result.entries.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getActivityHistory', () => {
|
||||
it('returns limited entries', () => {
|
||||
for (let i = 0; i < 5; i++) {
|
||||
emitActivity({ type: 'command_start', command: `history-test-${i}` });
|
||||
}
|
||||
const result = getActivityHistory(3);
|
||||
expect(result.entries.length).toBeLessThanOrEqual(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('subscribe', () => {
|
||||
it('receives new events', async () => {
|
||||
const received: any[] = [];
|
||||
const unsub = subscribe((entry) => received.push(entry));
|
||||
|
||||
emitActivity({ type: 'command_start', command: 'sub-test' });
|
||||
|
||||
// queueMicrotask is async — wait a tick
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
|
||||
expect(received.length).toBeGreaterThanOrEqual(1);
|
||||
expect(received[received.length - 1].command).toBe('sub-test');
|
||||
unsub();
|
||||
});
|
||||
|
||||
it('stops receiving after unsubscribe', async () => {
|
||||
const received: any[] = [];
|
||||
const unsub = subscribe((entry) => received.push(entry));
|
||||
unsub();
|
||||
|
||||
emitActivity({ type: 'command_start', command: 'should-not-see' });
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
|
||||
expect(received.filter(e => e.command === 'should-not-see').length).toBe(0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user