Skip to content
Merged
82 changes: 41 additions & 41 deletions api/generated-schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -1497,6 +1497,47 @@ type AccessUrlObject {
name: String
}

type ApiKeyResponse {
valid: Boolean!
error: String
}

type MinigraphqlResponse {
status: MinigraphStatus!
timeout: Int
error: String
}

"""The status of the minigraph"""
enum MinigraphStatus {
PRE_INIT
CONNECTING
CONNECTED
PING_FAILURE
ERROR_RETRYING
}

type CloudResponse {
status: String!
ip: String
error: String
}

type RelayResponse {
status: String!
timeout: String
error: String
}

type Cloud {
error: String
apiKey: ApiKeyResponse!
relay: RelayResponse
minigraphql: MinigraphqlResponse!
cloud: CloudResponse!
allowedOrigins: [String!]!
}

type RemoteAccess {
"""The type of WAN access used for Remote Access"""
accessType: WAN_ACCESS_TYPE!
Expand Down Expand Up @@ -1575,47 +1616,6 @@ type Network implements Node {
accessUrls: [AccessUrl!]
}

type ApiKeyResponse {
valid: Boolean!
error: String
}

type MinigraphqlResponse {
status: MinigraphStatus!
timeout: Int
error: String
}

"""The status of the minigraph"""
enum MinigraphStatus {
PRE_INIT
CONNECTING
CONNECTED
PING_FAILURE
ERROR_RETRYING
}

type CloudResponse {
status: String!
ip: String
error: String
}

type RelayResponse {
status: String!
timeout: String
error: String
}

type Cloud {
error: String
apiKey: ApiKeyResponse!
relay: RelayResponse
minigraphql: MinigraphqlResponse!
cloud: CloudResponse!
allowedOrigins: [String!]!
}

input AccessUrlObjectInput {
ipv4: String
ipv6: String
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { beforeEach, describe, expect, it } from 'vitest';

import { CloudService } from '../service/cloud.service.js';
import { CloudService } from '../connection-status/cloud.service.js';

const MOTHERSHIP_GRAPHQL_LINK = 'https://mothership.unraid.net/ws';
const API_VERSION = 'TEST_VERSION';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { ConfigService } from '@nestjs/config';

import { beforeEach, describe, expect, it, vi } from 'vitest';
import { faker } from '@faker-js/faker';
import * as fc from 'fast-check';
import { beforeEach, describe, expect, it, vi } from 'vitest';

import { ConfigType, DynamicRemoteAccessType } from '../model/connect-config.model.js';
import { ConnectConfigPersister } from '../service/config.persistence.js';
import { ConnectConfigPersister } from '../config/config.persistence.js';
import { ConfigType, DynamicRemoteAccessType } from '../config/connect.config.js';

describe('ConnectConfigPersister', () => {
let service: ConnectConfigPersister;
Expand Down Expand Up @@ -430,37 +430,34 @@ ssoSubIds="sub1,sub2"

it('should handle edge cases in port conversion', () => {
fc.assert(
fc.asyncProperty(
fc.integer({ min: 0, max: 65535 }),
async (port) => {
const legacyConfig = {
api: { version: '6.12.0', extraOrigins: '' },
local: { sandbox: 'no' },
remote: {
wanaccess: 'no',
wanport: port.toString(),
upnpEnabled: 'no',
apikey: 'unraid_test',
localApiKey: 'test_local',
email: 'test@example.com',
username: faker.internet.username(),
avatar: '',
regWizTime: '',
accesstoken: '',
idtoken: '',
refreshtoken: '',
dynamicRemoteAccessType: 'DISABLED',
ssoSubIds: '',
},
} as any;

const result = await service.convertLegacyConfig(legacyConfig);

// Test port conversion logic
expect(result.wanport).toBe(port);
expect(typeof result.wanport).toBe('number');
}
),
fc.asyncProperty(fc.integer({ min: 0, max: 65535 }), async (port) => {
const legacyConfig = {
api: { version: '6.12.0', extraOrigins: '' },
local: { sandbox: 'no' },
remote: {
wanaccess: 'no',
wanport: port.toString(),
upnpEnabled: 'no',
apikey: 'unraid_test',
localApiKey: 'test_local',
email: 'test@example.com',
username: faker.internet.username(),
avatar: '',
regWizTime: '',
accesstoken: '',
idtoken: '',
refreshtoken: '',
dynamicRemoteAccessType: 'DISABLED',
ssoSubIds: '',
},
} as any;

const result = await service.convertLegacyConfig(legacyConfig);

// Test port conversion logic
expect(result.wanport).toBe(port);
expect(typeof result.wanport).toBe('number');
}),
{ numRuns: 15 }
);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { ConfigService } from '@nestjs/config';

import { faker } from '@faker-js/faker';
import * as fc from 'fast-check';
import { beforeEach, describe, expect, it, vi } from 'vitest';

import { MyServersConfig, DynamicRemoteAccessType } from '../model/connect-config.model.js';
import { ConnectConfigPersister } from '../service/config.persistence.js';
import { ConnectConfigPersister } from '../config/config.persistence.js';
import { DynamicRemoteAccessType, MyServersConfig } from '../config/connect.config.js';

describe('MyServersConfig Validation', () => {
let persister: ConnectConfigPersister;
Expand All @@ -23,7 +24,7 @@ describe('MyServersConfig Validation', () => {
} as any;

persister = new ConnectConfigPersister(configService as any);

validConfig = {
wanaccess: false,
wanport: 0,
Expand Down Expand Up @@ -157,31 +158,24 @@ describe('MyServersConfig Validation', () => {

it('should handle various boolean combinations', () => {
fc.assert(
fc.asyncProperty(
fc.boolean(),
fc.boolean(),
async (wanaccess, upnpEnabled) => {
const config = { ...validConfig, wanaccess, upnpEnabled };
const result = await persister.validate(config);
expect(result.wanaccess).toBe(wanaccess);
expect(result.upnpEnabled).toBe(upnpEnabled);
}
),
fc.asyncProperty(fc.boolean(), fc.boolean(), async (wanaccess, upnpEnabled) => {
const config = { ...validConfig, wanaccess, upnpEnabled };
const result = await persister.validate(config);
expect(result.wanaccess).toBe(wanaccess);
expect(result.upnpEnabled).toBe(upnpEnabled);
}),
{ numRuns: 10 }
);
});

it('should handle valid port numbers', () => {
fc.assert(
fc.asyncProperty(
fc.integer({ min: 0, max: 65535 }),
async (port) => {
const config = { ...validConfig, wanport: port };
const result = await persister.validate(config);
expect(result.wanport).toBe(port);
expect(typeof result.wanport).toBe('number');
}
),
fc.asyncProperty(fc.integer({ min: 0, max: 65535 }), async (port) => {
const config = { ...validConfig, wanport: port };
const result = await persister.validate(config);
expect(result.wanport).toBe(port);
expect(typeof result.wanport).toBe('number');
}),
{ numRuns: 20 }
);
});
Expand Down Expand Up @@ -225,9 +219,9 @@ describe('MyServersConfig Validation', () => {
it('should reject invalid enum values', () => {
fc.assert(
fc.asyncProperty(
fc.string({ minLength: 1 }).filter(s =>
!Object.values(DynamicRemoteAccessType).includes(s as any)
),
fc
.string({ minLength: 1 })
.filter((s) => !Object.values(DynamicRemoteAccessType).includes(s as any)),
async (invalidEnumValue) => {
const config = { ...validConfig, dynamicRemoteAccessType: invalidEnumValue };
await expect(persister.validate(config)).rejects.toThrow();
Expand All @@ -240,9 +234,9 @@ describe('MyServersConfig Validation', () => {
it('should reject invalid email formats using fuzzing', () => {
fc.assert(
fc.asyncProperty(
fc.string({ minLength: 1 }).filter(s =>
!s.includes('@') || s.startsWith('@') || s.endsWith('@')
),
fc
.string({ minLength: 1 })
.filter((s) => !s.includes('@') || s.startsWith('@') || s.endsWith('@')),
async (invalidEmail) => {
const config = { ...validConfig, email: invalidEmail };
await expect(persister.validate(config)).rejects.toThrow();
Expand All @@ -254,15 +248,12 @@ describe('MyServersConfig Validation', () => {

it('should accept any number values for wanport (range validation is done at form level)', () => {
fc.assert(
fc.asyncProperty(
fc.integer({ min: -100000, max: 100000 }),
async (port) => {
const config = { ...validConfig, wanport: port };
const result = await persister.validate(config);
expect(result.wanport).toBe(port);
expect(typeof result.wanport).toBe('number');
}
),
fc.asyncProperty(fc.integer({ min: -100000, max: 100000 }), async (port) => {
const config = { ...validConfig, wanport: port };
const result = await persister.validate(config);
expect(result.wanport).toBe(port);
expect(typeof result.wanport).toBe('number');
}),
{ numRuns: 10 }
);
});
Expand Down Expand Up @@ -310,4 +301,4 @@ describe('MyServersConfig Validation', () => {
);
});
});
});
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';

import { MothershipGraphqlClientService } from '../service/graphql.client.js';
import { MinigraphStatus } from '../model/connect-config.model.js';
import { MinigraphStatus } from '../config/connect.config.js';
import { MothershipGraphqlClientService } from '../mothership-proxy/graphql.client.js';

// Mock only the WebSocket client creation, not the Apollo Client error handling
vi.mock('graphql-ws', () => ({
Expand Down Expand Up @@ -87,7 +87,8 @@ describe('MothershipGraphqlClientService', () => {
{
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"',
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,
},
Expand Down Expand Up @@ -127,7 +128,7 @@ describe('MothershipGraphqlClientService', () => {
// 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();
Expand Down Expand Up @@ -157,4 +158,4 @@ describe('MothershipGraphqlClientService', () => {
expect(service.mothershipGraphqlLink).toBe('https://mothership.unraid.net/ws');
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import type { Mock } from 'vitest';
import { URL_TYPE } from '@unraid/shared/network.model.js';
import { beforeEach, describe, expect, it, vi } from 'vitest';

import { ConfigType } from '../model/connect-config.model.js';
import { UrlResolverService } from '../service/url-resolver.service.js';
import { ConfigType } from '../config/connect.config.js';
import { UrlResolverService } from '../network/url-resolver.service.js';

interface PortTestParams {
httpPort: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import { parse as parseIni } from 'ini';
import { isEqual } from 'lodash-es';
import { bufferTime } from 'rxjs/operators';

import type { MyServersConfig as LegacyConfig } from '../model/my-servers-config.model.js';
import { ConfigType, MyServersConfig } from '../model/connect-config.model.js';
import type { MyServersConfig as LegacyConfig } from './my-servers.config.js';
import { ConfigType, MyServersConfig } from './connect.config.js';

@Injectable()
export class ConnectConfigPersister implements OnModuleInit, OnModuleDestroy {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ConfigService } from '@nestjs/config';
import { OnEvent } from '@nestjs/event-emitter';

import { EVENTS } from '../helper/nest-tokens.js';
import { ConfigType, emptyMyServersConfig, MyServersConfig } from '../model/connect-config.model.js';
import { ConfigType, emptyMyServersConfig, MyServersConfig } from './connect.config.js';

@Injectable()
export class ConnectConfigService {
Expand Down
Loading