Skip to content

Commit ddcf539

Browse files
authored
fix(actor): read /v1/actor response from top level instead of res.actor (#32)
Huntress's GET /v1/actor returns the actor shape ({ reseller, account, user }) at the TOP LEVEL of the response body — unlike most other endpoints, it is not wrapped in { actor: ... }. ActorResource.get() requested { actor: Actor } and returned res.actor, which was always undefined in production. Downstream, huntress-mcp JSON.stringify(undefined) produced a { type: 'text', text: undefined } content block, failing MCP Zod validation (invalid_union on content[0]) on every huntress_accounts_actor call — surfaced by the EpiOn tool audit. The unit test never caught it because the MSW mock wrapped the payload as { actor: {...} }, mirroring the SDK's wrong assumption. The mock now serves the real unwrapped shape and the test asserts the full Actor.
1 parent 40fe583 commit ddcf539

3 files changed

Lines changed: 13 additions & 5 deletions

File tree

src/resources/actor.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ export class ActorResource {
55
constructor(private readonly http: HttpClient) {}
66

77
async get(): Promise<Actor> {
8-
const res = await this.http.request<{ actor: Actor }>('/v1/actor');
9-
return res.actor;
8+
// Unlike most Huntress endpoints, /v1/actor returns the actor shape at
9+
// the top level of the response — it is NOT wrapped in { actor: ... }.
10+
return this.http.request<Actor>('/v1/actor');
1011
}
1112
}

tests/mocks/handlers.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,13 @@ export const handlers = [
99
})
1010
),
1111

12+
// The real /v1/actor endpoint returns the actor shape at the TOP LEVEL —
13+
// it is NOT wrapped in { actor: ... } like most other Huntress endpoints.
1214
http.get(`${BASE_URL}/v1/actor`, () =>
1315
HttpResponse.json({
14-
actor: { reseller: null, account: { id: 1, name: 'Test Account' }, user: { id: 1, email: 'test@example.com', name: 'Test User' } },
16+
reseller: null,
17+
account: { id: 1, name: 'Test Account' },
18+
user: { id: 1, email: 'test@example.com', name: 'Test User' },
1519
})
1620
),
1721

tests/unit/client.test.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@ describe('HuntressClient', () => {
1313
});
1414

1515
describe('actor', () => {
16-
it('should get actor', async () => {
16+
it('should get actor from the unwrapped top-level response', async () => {
1717
const actor = await client.actor.get();
18-
expect(actor.user?.email).toBe('test@example.com');
18+
expect(actor).toBeDefined();
19+
expect(actor.reseller).toBeNull();
20+
expect(actor.account).toEqual({ id: 1, name: 'Test Account' });
21+
expect(actor.user).toEqual({ id: 1, email: 'test@example.com', name: 'Test User' });
1922
});
2023
});
2124

0 commit comments

Comments
 (0)