mirror of
https://github.com/unraid/api.git
synced 2025-12-31 13:39:52 -06:00
fix: sign out correctly on error (#1452)
This actually doesn't fix the error but should make it more flexible. Also adds some testing. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Summary by CodeRabbit * **Refactor** * Enhanced error detection for invalid API key messages to improve accuracy. * **Tests** * Added comprehensive tests verifying error handling and client lifecycle behavior without network dependencies. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -184,7 +184,7 @@ export class MothershipGraphqlClientService implements OnModuleInit, OnModuleDes
|
||||
* Check if an error is an invalid API key error
|
||||
*/
|
||||
private isInvalidApiKeyError(error: unknown): boolean {
|
||||
return error instanceof Error && error.message.includes('API Key Invalid');
|
||||
return typeof error === 'object' && error !== null && 'message' in error && typeof error.message === 'string' && error.message.includes('API Key Invalid');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { MothershipGraphqlClientService } from '../service/graphql.client.js';
|
||||
import { MinigraphStatus } from '../model/connect-config.model.js';
|
||||
|
||||
// Mock only the WebSocket client creation, not the Apollo Client error handling
|
||||
vi.mock('graphql-ws', () => ({
|
||||
createClient: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock WebSocket to avoid actual network connections
|
||||
vi.mock('ws', () => ({
|
||||
WebSocket: vi.fn().mockImplementation(() => ({})),
|
||||
}));
|
||||
|
||||
describe('MothershipGraphqlClientService', () => {
|
||||
let service: MothershipGraphqlClientService;
|
||||
let mockConfigService: any;
|
||||
let mockConnectionService: any;
|
||||
let mockEventEmitter: any;
|
||||
let mockWsClient: any;
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
mockConfigService = {
|
||||
getOrThrow: vi.fn((key: string) => {
|
||||
switch (key) {
|
||||
case 'API_VERSION':
|
||||
return '4.8.0+test';
|
||||
case 'MOTHERSHIP_GRAPHQL_LINK':
|
||||
return 'https://mothership.unraid.net/ws';
|
||||
default:
|
||||
throw new Error(`Unknown config key: ${key}`);
|
||||
}
|
||||
}),
|
||||
set: vi.fn(),
|
||||
};
|
||||
|
||||
mockConnectionService = {
|
||||
getIdentityState: vi.fn().mockReturnValue({ isLoaded: true }),
|
||||
getWebsocketConnectionParams: vi.fn().mockReturnValue({}),
|
||||
getMothershipWebsocketHeaders: vi.fn().mockReturnValue({}),
|
||||
getConnectionState: vi.fn().mockReturnValue({ status: MinigraphStatus.CONNECTED }),
|
||||
setConnectionStatus: vi.fn(),
|
||||
receivePing: vi.fn(),
|
||||
};
|
||||
|
||||
mockEventEmitter = {
|
||||
emit: vi.fn(),
|
||||
};
|
||||
|
||||
mockWsClient = {
|
||||
on: vi.fn().mockReturnValue(() => {}),
|
||||
terminate: vi.fn(),
|
||||
dispose: vi.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
|
||||
// Mock the createClient function
|
||||
const { createClient } = await import('graphql-ws');
|
||||
vi.mocked(createClient).mockReturnValue(mockWsClient as any);
|
||||
|
||||
service = new MothershipGraphqlClientService(
|
||||
mockConfigService as any,
|
||||
mockConnectionService as any,
|
||||
mockEventEmitter as any
|
||||
);
|
||||
});
|
||||
|
||||
describe('isInvalidApiKeyError', () => {
|
||||
it.each([
|
||||
{
|
||||
description: 'standard API key error',
|
||||
error: { message: 'API Key Invalid with error No user found' },
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
description: 'simple API key error',
|
||||
error: { message: 'API Key Invalid' },
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
description: 'API key error within other text',
|
||||
error: { message: 'Something else API Key Invalid something' },
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
description: 'malformed GraphQL error with API key message',
|
||||
error: {
|
||||
message: '"error" message expects the \'payload\' property to be an array of GraphQL errors, but got "API Key Invalid with error No user found"',
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
description: 'non-API key error',
|
||||
error: { message: 'Network connection failed' },
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
description: 'null error',
|
||||
error: null,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
description: 'empty error object',
|
||||
error: {},
|
||||
expected: false,
|
||||
},
|
||||
])('should identify $description correctly', ({ error, expected }) => {
|
||||
const isInvalidApiKeyError = (service as any).isInvalidApiKeyError.bind(service);
|
||||
expect(isInvalidApiKeyError(error)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('client lifecycle', () => {
|
||||
it('should return null client when identity state is not valid', () => {
|
||||
mockConnectionService.getIdentityState.mockReturnValue({ isLoaded: false });
|
||||
|
||||
const client = service.getClient();
|
||||
|
||||
expect(client).toBeNull();
|
||||
});
|
||||
|
||||
it('should return client when identity state is valid', () => {
|
||||
mockConnectionService.getIdentityState.mockReturnValue({ isLoaded: true });
|
||||
|
||||
// Since we're not mocking Apollo Client, this will create a real client
|
||||
// We just want to verify the state check works
|
||||
const client = service.getClient();
|
||||
|
||||
// The client should either be null (if not created yet) or an Apollo client instance
|
||||
// The key is that it doesn't throw an error when state is valid
|
||||
expect(() => service.getClient()).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('sendQueryResponse', () => {
|
||||
it('should handle null client gracefully', async () => {
|
||||
// Make identity state invalid so getClient returns null
|
||||
mockConnectionService.getIdentityState.mockReturnValue({ isLoaded: false });
|
||||
|
||||
const result = await service.sendQueryResponse('test-sha256', {
|
||||
data: { test: 'data' },
|
||||
});
|
||||
|
||||
// Should not throw and should return undefined when client is null
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('configuration', () => {
|
||||
it('should get API version from config', () => {
|
||||
expect(service.apiVersion).toBe('4.8.0+test');
|
||||
});
|
||||
|
||||
it('should get mothership GraphQL link from config', () => {
|
||||
expect(service.mothershipGraphqlLink).toBe('https://mothership.unraid.net/ws');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user