Skip to content

Commit b471dba

Browse files
authored
Merge pull request #4 from superfaceai/feat/unauthorize_handling
Handle unauthorized requests
2 parents 1de7b08 + 5fea4fe commit b471dba

File tree

3 files changed

+140
-1
lines changed

3 files changed

+140
-1
lines changed

typescript/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "superface",
3-
"version": "0.1.0-rc.6",
3+
"version": "0.1.0-rc.7",
44
"description": "Intelligent Tools for Agentic AI",
55
"author": "Superface Team <[email protected]>",
66
"repository": {

typescript/src/client/index.test.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ describe('SuperfaceClient', () => {
1212

1313
beforeEach(() => {
1414
fetchSpy = jest.spyOn(global, 'fetch').mockResolvedValue({
15+
ok: true,
16+
status: 200,
1517
json: () =>
1618
Promise.resolve([
1719
{
@@ -50,6 +52,92 @@ describe('SuperfaceClient', () => {
5052
expect(fetchSpy).toHaveBeenCalledTimes(2);
5153
jest.useRealTimers();
5254
});
55+
56+
it('throws when user is not authenticated', async () => {
57+
fetchSpy.mockReset().mockResolvedValue({
58+
ok: false,
59+
status: 401,
60+
json: () =>
61+
Promise.resolve({
62+
title: 'Unauthorized',
63+
detail: `Toolhub URL (https://pod.superface.ai) for Function Descriptors is unauthorized`,
64+
}),
65+
} as Response);
66+
67+
const client = new Superface({ apiKey: 'stub', cacheTimeout: 50 });
68+
69+
expect(client.getTools()).rejects.toThrow(SuperfaceError);
70+
});
71+
});
72+
73+
describe('runTool', () => {
74+
let fetchSpy: jest.SpyInstance;
75+
76+
beforeEach(() => {
77+
fetchSpy = jest.spyOn(global, 'fetch').mockResolvedValue({
78+
ok: true,
79+
status: 200,
80+
json: () =>
81+
Promise.resolve([
82+
{
83+
type: 'function',
84+
function: {
85+
name: 'stub',
86+
description: 'description',
87+
parameters: {},
88+
},
89+
},
90+
]),
91+
} as Response);
92+
});
93+
94+
afterEach(() => {
95+
fetchSpy.mockRestore();
96+
});
97+
98+
it('throws when user is not authenticated', async () => {
99+
fetchSpy.mockReset().mockResolvedValue({
100+
ok: false,
101+
status: 401,
102+
json: () =>
103+
Promise.resolve({
104+
title: 'Unauthorized',
105+
detail: `Toolhub URL (https://pod.superface.ai) for Function Descriptors is unauthorized`,
106+
}),
107+
} as Response);
108+
109+
const client = new Superface({ apiKey: 'stub' });
110+
111+
expect(
112+
client.runTool({ userId: 'example_user', name: 'stub', args: {} })
113+
).rejects.toThrow(SuperfaceError);
114+
});
115+
});
116+
117+
describe('linkToUserConnections', () => {
118+
let fetchSpy: jest.SpyInstance;
119+
120+
afterEach(() => {
121+
fetchSpy.mockRestore();
122+
});
123+
124+
it('throws when user is not authenticated', async () => {
125+
fetchSpy = jest.spyOn(global, 'fetch').mockResolvedValue({
126+
ok: false,
127+
status: 401,
128+
json: () =>
129+
Promise.resolve({
130+
title: 'Unauthorized',
131+
detail: `Toolhub URL (https://pod.superface.ai) for Function Descriptors is unauthorized`,
132+
}),
133+
} as Response);
134+
135+
const client = new Superface({ apiKey: 'stub' });
136+
137+
expect(
138+
client.linkToUserConnections({ userId: 'example_user' })
139+
).rejects.toThrow(SuperfaceError);
140+
});
53141
});
54142
});
55143

typescript/src/client/index.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,23 @@ export class Superface {
207207
continue;
208208
}
209209

210+
if (!response.ok) {
211+
if (response.status === 401) {
212+
const body = (await response.json()) as {
213+
title: string;
214+
detail: string;
215+
};
216+
217+
throw new SuperfaceError(`${body.title}. ${body.detail}`);
218+
}
219+
220+
lastError = new SuperfaceError(
221+
`Failed to fetch tool definitions. HTTP status ${response.status}`
222+
);
223+
await delay(attempt);
224+
continue;
225+
}
226+
210227
let body: ToolDefinition[];
211228
try {
212229
body = await response.json();
@@ -303,6 +320,23 @@ export class Superface {
303320
headers,
304321
});
305322

323+
if (!response.ok) {
324+
if (response.status === 401) {
325+
const body = (await response.json()) as {
326+
title: string;
327+
detail: string;
328+
};
329+
330+
throw new SuperfaceError(`${body.title}. ${body.detail}`);
331+
}
332+
333+
lastErrorResult = {
334+
status: 'error',
335+
error: `Failed to connect to Superface. HTTP status ${response.status}`,
336+
assistant_hint: 'Please try again.',
337+
};
338+
}
339+
306340
const contentType = response.headers.get('content-type');
307341
if (contentType && contentType.includes('application/json')) {
308342
try {
@@ -382,6 +416,23 @@ export class Superface {
382416
headers,
383417
});
384418

419+
if (!response.ok) {
420+
if (response.status === 401) {
421+
const body = (await response.json()) as {
422+
title: string;
423+
detail: string;
424+
};
425+
426+
throw new SuperfaceError(`${body.title}. ${body.detail}`);
427+
}
428+
429+
lastError = new SuperfaceError(
430+
`Failed to fetch configuration link. HTTP status ${response.status}`
431+
);
432+
await delay(attempt);
433+
continue;
434+
}
435+
385436
const body = (await response.json()) as {
386437
status: 'success';
387438
configuration_url: string;

0 commit comments

Comments
 (0)