__tests__/tools.test.js
Rama main · réplica de lectura del repositorio original.
/**
* tools/ — the tools extracted out of run().
*
* WHY THESE TESTS ARE SHORT, and the whole point of the split: every one of these behaviours was
* already covered, but only through the entire agent loop — a fake page, a scripted model, a job
* store, fifty lines of setup to prove that "save this sentence" saves a sentence. A tool that is
* its own module is testable as one, which is what makes the next tool cheap to add.
*/
import { describe, it, expect, vi } from 'vitest';
import * as registry from '../src/tools/index.js';
import profiles from '../src/tools/profiles.js';
function ctx(over = {}) {
const steps = [], said = [];
return {
steps, said,
observe: (t) => said.push(t),
step: (kind, text) => steps.push({ kind, text }),
switchedSession: vi.fn(),
describeProfiles: () => 'fb — facebook (in use)',
page: () => ({ url: () => 'https://example.test/here' }),
session: () => ({ id: 's1', profile: 'fb' }),
setSession: vi.fn(),
switchProfile: null,
...over,
};
}
describe('the registry', () => {
it('claims exactly the tools that have been moved, and nothing else', () => {
// The loop falls through to its remaining switch for anything absent, so a name claimed here
// and not implemented would silently do nothing at all.
for (const name of ['list_profiles', 'use_profile', 'remember_about_me', 'save_my_writing',
'describe_my_voice', 'waiting_on', 'whose_is_this', 'record_reply',
'conversation', 'save_gig', 'save_reach', 'save_opportunity',
'diagnostics', 'note', 'finish',
'scroll', 'back', 'look', 'read', 'open']) {
expect(registry.has(name), name).toBe(true);
expect(typeof registry.REGISTRY[name], name).toBe('function');
}
/* Still in the switch. These are the ones the GUARDS act on — a click can be refused, a type
can publish, an act waits for a person — so they move last, where a mistake is hardest to see
and the policy that protects a real account is nearest. */
for (const notYet of ['click', 'type', 'act', 'sweep', 'google', 'dig', 'save_lead']) {
expect(registry.has(notYet), notYet).toBe(false);
}
});
it('never claims a name it cannot run', () => {
for (const [name, fn] of Object.entries(registry.REGISTRY)) {
expect(typeof fn, name).toBe('function');
}
});
});
describe('use_profile — a switch is a different browser, not a setting', () => {
it('hands the new session back, so the loop stops driving the old one', async () => {
/*
* The bug this guards: every helper reads the CURRENT page, and a switch that updated only some
* of them left the agent working the browser it had left while the person watched the new one
* do nothing.
*/
const next = { id: 's2', profile: 'li' };
const c = ctx({ switchProfile: vi.fn(async () => next) });
await profiles.use_profile(c, { profile: 'li' });
expect(c.setSession).toHaveBeenCalledWith(next);
expect(c.switchedSession).toHaveBeenCalledWith({ sessionId: 's2', profile: 'li' });
expect(c.said.join(' ')).toMatch(/Switched to "li"/);
});
it('says so plainly when switching is not available in this run', async () => {
const c = ctx({ switchProfile: null });
await profiles.use_profile(c, { profile: 'li' });
expect(c.said[0]).toMatch(/not available in this run/);
expect(c.setSession).not.toHaveBeenCalled();
});
it('list_profiles reads, and does not open anything', async () => {
const c = ctx();
await profiles.list_profiles(c);
expect(c.said[0]).toMatch(/fb — facebook/);
expect(c.setSession).not.toHaveBeenCalled();
});
});
describe('current_url', () => {
it('reads a youtube channel id out of the path so a link can be built, not guessed', async () => {
const c = ctx({ page: () => ({ url: () => 'https://studio.youtube.com/channel/UCabc123DEF/editing/branding', title: async () => 'YouTube Studio' }) });
await registry.run('current_url', c);
expect(c.said.join(' ')).toMatch(/UCabc123DEF/);
expect(c.said.join(' ')).toMatch(/studio\.youtube\.com\/channel\/UCabc123DEF/);
});
it('still reports the plain URL when there is no id in the path', async () => {
const c = ctx({ page: () => ({ url: () => 'https://example.test/here', title: async () => 'X' }) });
await registry.run('current_url', c);
expect(c.said.join(' ')).toMatch(/example\.test\/here/);
});
});