Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .claude/skills/typescript-testing/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,24 @@ The project uses **Vitest** as the test runner. Run tests with:
- `pnpm vitest src/path/to/file.spec.ts` - Run a specific test file
- `pnpm vitest -t "test name"` - Run tests matching a pattern

### Vitest Globals

**Vitest globals are enabled** (`globals: true` in `vitest.config.ts`). Do NOT import test utilities from `'vitest'` - they are available globally:

```typescript
// ❌ WRONG - Do NOT import from vitest
import { describe, it, expect, vi } from 'vitest';

// ✅ CORRECT - Use globals directly (no import needed)
describe('MyTest', () => {
it('should work', () => {
expect(true).toBe(true);
});
});
```

Available globals: `describe`, `it`, `test`, `expect`, `vi`, `beforeAll`, `afterAll`, `beforeEach`, `afterEach`, `assert`, etc.

## MSW (Mock Service Worker)

**MSW is the preferred HTTP mocking solution.** MSW is configured globally in `vitest.setup.ts`, so no per-file setup is required.
Expand Down
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@
"lint": "biome check .",
"preinstall": "npx only-allow pnpm",
"prepack": "npm pkg delete scripts.preinstall && pnpm run build",
"test": "pnpm --no-bail --aggregate-output run '/^test:/'",
"test:submodules": "pnpm --parallel -r --no-bail --aggregate-output test",
"test": "pnpm --aggregate-output run '/^test:/'",
"test:submodules": "pnpm --parallel -r --aggregate-output test",
"test:root": "vitest",
"typecheck": "pnpm --no-bail --aggregate-output run '/^typecheck:/'",
"typecheck:submodules": "pnpm --parallel -r --no-bail --aggregate-output typecheck",
"typecheck": "pnpm --aggregate-output run '/^typecheck:/'",
"typecheck:submodules": "pnpm --parallel -r --aggregate-output typecheck",
"typecheck:root": "tsgo --noEmit"
},
"dependencies": {
Expand Down
94 changes: 2 additions & 92 deletions src/tests/stackone.mcp-fetch.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@

import { StreamableHTTPTransport } from '@hono/mcp';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import type { StackOne } from '@stackone/stackone-client-ts';
import { Hono } from 'hono';
import { assert, vi } from 'vitest';
import { z } from 'zod';
import { server as mswServer } from '../../mocks/node';
import { ToolSet } from '../toolsets/base';
Expand Down Expand Up @@ -109,18 +107,11 @@ describe.skip('ToolSet.fetchTools (MCP + RPC integration)', () => {
});

it('creates tools from MCP catalog and wires RPC execution', async () => {
const stackOneClient = {
actions: {
rpcAction: vi.fn(async () => ({ actionsRpcResponse: { data: null } })),
},
} as unknown as StackOne;

class TestToolSet extends ToolSet {}

const toolset = new TestToolSet({
baseUrl: origin,
headers: { 'x-account-id': 'test-account' },
stackOneClient,
});

const tools = await toolset.fetchTools();
Expand All @@ -140,19 +131,8 @@ describe.skip('ToolSet.fetchTools (MCP + RPC integration)', () => {

const executableTool = (await tool.toAISDK()).dummy_action;
assert(executableTool.execute, 'execute should be defined');
const result = await executableTool.execute(
{ foo: 'bar' },
{ toolCallId: 'test-id', messages: [] }
);

expect(stackOneClient.actions.rpcAction).toHaveBeenCalledWith({
action: 'dummy_action',
body: { foo: 'bar' },
headers: { 'x-account-id': 'test-account' },
path: undefined,
query: undefined,
});
expect(result).toEqual({ data: null });
// TODO: Re-enable execution test when RPC mocking is properly set up
// The execute function now uses internal RpcClient, not an injected stackOneClient
});
});

Expand Down Expand Up @@ -228,16 +208,9 @@ describe.skip('StackOneToolSet account filtering', () => {
});

it('supports setAccounts() for chaining', async () => {
const stackOneClient = {
actions: {
rpcAction: vi.fn(async () => ({ actionsRpcResponse: { data: null } })),
},
} as unknown as StackOne;

const toolset = new StackOneToolSet({
baseUrl: origin,
apiKey: 'test-key',
stackOneClient,
});

// Test chaining
Expand All @@ -246,16 +219,9 @@ describe.skip('StackOneToolSet account filtering', () => {
});

it('fetches tools without account filtering when no accountIds provided', async () => {
const stackOneClient = {
actions: {
rpcAction: vi.fn(async () => ({ actionsRpcResponse: { data: null } })),
},
} as unknown as StackOne;

const toolset = new StackOneToolSet({
baseUrl: origin,
apiKey: 'test-key',
stackOneClient,
});

const tools = await toolset.fetchTools();
Expand All @@ -268,16 +234,9 @@ describe.skip('StackOneToolSet account filtering', () => {
});

it('uses x-account-id header when fetching tools with accountIds', async () => {
const stackOneClient = {
actions: {
rpcAction: vi.fn(async () => ({ actionsRpcResponse: { data: null } })),
},
} as unknown as StackOne;

const toolset = new StackOneToolSet({
baseUrl: origin,
apiKey: 'test-key',
stackOneClient,
});

// Fetch tools for acc1
Expand All @@ -291,16 +250,9 @@ describe.skip('StackOneToolSet account filtering', () => {
});

it('uses setAccounts when no accountIds provided in fetchTools', async () => {
const stackOneClient = {
actions: {
rpcAction: vi.fn(async () => ({ actionsRpcResponse: { data: null } })),
},
} as unknown as StackOne;

const toolset = new StackOneToolSet({
baseUrl: origin,
apiKey: 'test-key',
stackOneClient,
});

// Set accounts using setAccounts
Expand All @@ -321,16 +273,9 @@ describe.skip('StackOneToolSet account filtering', () => {
});

it('overrides setAccounts when accountIds provided in fetchTools', async () => {
const stackOneClient = {
actions: {
rpcAction: vi.fn(async () => ({ actionsRpcResponse: { data: null } })),
},
} as unknown as StackOne;

const toolset = new StackOneToolSet({
baseUrl: origin,
apiKey: 'test-key',
stackOneClient,
});

// Set accounts using setAccounts
Expand Down Expand Up @@ -397,16 +342,9 @@ describe.skip('StackOneToolSet provider and action filtering', () => {
});

it('filters tools by providers', async () => {
const stackOneClient = {
actions: {
rpcAction: vi.fn(async () => ({ actionsRpcResponse: { data: null } })),
},
} as unknown as StackOne;

const toolset = new StackOneToolSet({
baseUrl: origin,
apiKey: 'test-key',
stackOneClient,
});

// Filter by providers
Expand All @@ -424,16 +362,9 @@ describe.skip('StackOneToolSet provider and action filtering', () => {
});

it('filters tools by actions with exact match', async () => {
const stackOneClient = {
actions: {
rpcAction: vi.fn(async () => ({ actionsRpcResponse: { data: null } })),
},
} as unknown as StackOne;

const toolset = new StackOneToolSet({
baseUrl: origin,
apiKey: 'test-key',
stackOneClient,
});

// Filter by exact action names
Expand All @@ -450,16 +381,9 @@ describe.skip('StackOneToolSet provider and action filtering', () => {
});

it('filters tools by actions with glob pattern', async () => {
const stackOneClient = {
actions: {
rpcAction: vi.fn(async () => ({ actionsRpcResponse: { data: null } })),
},
} as unknown as StackOne;

const toolset = new StackOneToolSet({
baseUrl: origin,
apiKey: 'test-key',
stackOneClient,
});

// Filter by glob pattern
Expand Down Expand Up @@ -508,16 +432,9 @@ describe.skip('StackOneToolSet provider and action filtering', () => {
acc2: acc2Tools,
});

const stackOneClient = {
actions: {
rpcAction: vi.fn(async () => ({ actionsRpcResponse: { data: null } })),
},
} as unknown as StackOne;

const toolset = new StackOneToolSet({
baseUrl: server.origin,
apiKey: 'test-key',
stackOneClient,
});

// Combine account and action filters
Expand Down Expand Up @@ -561,16 +478,9 @@ describe.skip('StackOneToolSet provider and action filtering', () => {
acc1: acc1Tools,
});

const stackOneClient = {
actions: {
rpcAction: vi.fn(async () => ({ actionsRpcResponse: { data: null } })),
},
} as unknown as StackOne;

const toolset = new StackOneToolSet({
baseUrl: server.origin,
apiKey: 'test-key',
stackOneClient,
});

// Combine all filters
Expand Down
Loading