Compare commits
4 Commits
3a04e09f13
...
ec128d54b4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec128d54b4 | ||
|
|
cedea4072b | ||
|
|
1e650262a4 | ||
|
|
f1980cf425 |
@@ -46,6 +46,20 @@ describe('isIpAllowed', () => {
|
||||
expect(isIpAllowed(ip).ok).toBe(false);
|
||||
});
|
||||
|
||||
// IP-level bypass vectors ported from the safety-coverage branch. CGNAT
|
||||
// (100.64/10) and the ULA range (fc00::/7) are already exercised above with
|
||||
// other sample addresses; the genuinely distinct case is the IPv4-mapped
|
||||
// IPv6 *loopback* (::ffff:127.0.0.1) — the table above only had the mapped
|
||||
// *private* variant. fd00::/8 is the commonly-assigned ULA prefix, kept as an
|
||||
// explicit regression guard.
|
||||
it.each([
|
||||
['CGNAT', '100.64.0.1'],
|
||||
['ULA fd00::/8', 'fd00::1'],
|
||||
['IPv4-mapped IPv6 loopback', '::ffff:127.0.0.1'],
|
||||
])('blocks bypass vector %s (%s)', (_label, ip) => {
|
||||
expect(isIpAllowed(ip).ok).toBe(false);
|
||||
});
|
||||
|
||||
it('allows a public IPv4 (8.8.8.8)', () => {
|
||||
expect(isIpAllowed('8.8.8.8').ok).toBe(true);
|
||||
});
|
||||
|
||||
61
apps/server/src/integrations/ai/ai-error.util.spec.ts
Normal file
61
apps/server/src/integrations/ai/ai-error.util.spec.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { describeProviderError } from './ai-error.util';
|
||||
|
||||
/**
|
||||
* Unit tests for describeProviderError: the shared formatter used both for the
|
||||
* server log line and for the error text streamed back to the client. This
|
||||
* pins the behaviour, including the one behaviour change introduced when the
|
||||
* two inline formatters were unified: a truncated, single-line snippet of the
|
||||
* provider `responseBody`/`text` is appended (so a misconfigured endpoint's
|
||||
* HTML error page is diagnosable). The util guarantees the API key is never in
|
||||
* the response body, so this is safe to surface.
|
||||
*/
|
||||
describe('describeProviderError', () => {
|
||||
it('uses the fallback for a null/empty/undefined error', () => {
|
||||
expect(describeProviderError(null, 'AI stream error')).toBe(
|
||||
'AI stream error',
|
||||
);
|
||||
expect(describeProviderError('', 'AI stream error')).toBe('AI stream error');
|
||||
expect(describeProviderError(undefined)).toBe('Unknown error');
|
||||
});
|
||||
|
||||
it('returns a non-empty plain string error as-is', () => {
|
||||
expect(describeProviderError('boom')).toBe('boom');
|
||||
});
|
||||
|
||||
it('formats statusCode + message', () => {
|
||||
expect(
|
||||
describeProviderError({ statusCode: 401, message: 'Unauthorized' }),
|
||||
).toBe('401: Unauthorized');
|
||||
});
|
||||
|
||||
it('falls back to message when there is no statusCode', () => {
|
||||
expect(describeProviderError({ message: 'nope' })).toBe('nope');
|
||||
});
|
||||
|
||||
it('appends a whitespace-collapsed response body snippet', () => {
|
||||
const out = describeProviderError({
|
||||
statusCode: 502,
|
||||
message: 'Bad Gateway',
|
||||
responseBody: '<html>\n <body>upstream error</body>\n</html>',
|
||||
});
|
||||
expect(out.startsWith('502: Bad Gateway | response body: ')).toBe(true);
|
||||
// Newlines and runs of spaces are collapsed to single spaces.
|
||||
expect(out).toContain('<html> <body>upstream error</body> </html>');
|
||||
});
|
||||
|
||||
it('reads `text` when responseBody is absent', () => {
|
||||
expect(describeProviderError({ message: 'e', text: 'body-text' })).toBe(
|
||||
'e | response body: body-text',
|
||||
);
|
||||
});
|
||||
|
||||
it('truncates a long body to 300 chars + ellipsis', () => {
|
||||
const out = describeProviderError({
|
||||
message: 'e',
|
||||
responseBody: 'x'.repeat(500),
|
||||
});
|
||||
expect(out).toContain('…');
|
||||
// 'e | response body: ' + 300 chars + '…'
|
||||
expect(out.length).toBeLessThan('e | response body: '.length + 305);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user