refactor: remove now un-used system queries/mutations/resolvers from both client and server
This commit is contained in:
parent
3cc3c9011e
commit
7d9c8a75a0
19 changed files with 11 additions and 869 deletions
|
@ -98,18 +98,6 @@ export enum AppSupportedArchitecturesEnum {
|
|||
Arm64 = 'ARM64',
|
||||
}
|
||||
|
||||
export type Cpu = {
|
||||
__typename?: 'Cpu';
|
||||
load: Scalars['Float'];
|
||||
};
|
||||
|
||||
export type DiskMemory = {
|
||||
__typename?: 'DiskMemory';
|
||||
available: Scalars['Float'];
|
||||
total: Scalars['Float'];
|
||||
used: Scalars['Float'];
|
||||
};
|
||||
|
||||
export enum FieldTypesEnum {
|
||||
Email = 'email',
|
||||
Fqdn = 'fqdn',
|
||||
|
@ -146,11 +134,9 @@ export type Mutation = {
|
|||
login: TokenResponse;
|
||||
logout: Scalars['Boolean'];
|
||||
register: TokenResponse;
|
||||
restart: Scalars['Boolean'];
|
||||
startApp: App;
|
||||
stopApp: App;
|
||||
uninstallApp: App;
|
||||
update: Scalars['Boolean'];
|
||||
updateApp: App;
|
||||
updateAppConfig: App;
|
||||
};
|
||||
|
@ -195,21 +181,12 @@ export type Query = {
|
|||
listAppsInfo: ListAppsResonse;
|
||||
me?: Maybe<User>;
|
||||
refreshToken?: Maybe<TokenResponse>;
|
||||
systemInfo?: Maybe<SystemInfoResponse>;
|
||||
version: VersionResponse;
|
||||
};
|
||||
|
||||
export type QueryGetAppArgs = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
export type SystemInfoResponse = {
|
||||
__typename?: 'SystemInfoResponse';
|
||||
cpu: Cpu;
|
||||
disk: DiskMemory;
|
||||
memory: DiskMemory;
|
||||
};
|
||||
|
||||
export type TokenResponse = {
|
||||
__typename?: 'TokenResponse';
|
||||
token: Scalars['String'];
|
||||
|
@ -235,12 +212,6 @@ export type UsernamePasswordInput = {
|
|||
username: Scalars['String'];
|
||||
};
|
||||
|
||||
export type VersionResponse = {
|
||||
__typename?: 'VersionResponse';
|
||||
current: Scalars['String'];
|
||||
latest?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type InstallAppMutationVariables = Exact<{
|
||||
input: AppInputType;
|
||||
}>;
|
||||
|
@ -263,10 +234,6 @@ export type RegisterMutationVariables = Exact<{
|
|||
|
||||
export type RegisterMutation = { __typename?: 'Mutation'; register: { __typename?: 'TokenResponse'; token: string } };
|
||||
|
||||
export type RestartMutationVariables = Exact<{ [key: string]: never }>;
|
||||
|
||||
export type RestartMutation = { __typename?: 'Mutation'; restart: boolean };
|
||||
|
||||
export type StartAppMutationVariables = Exact<{
|
||||
id: Scalars['String'];
|
||||
}>;
|
||||
|
@ -285,10 +252,6 @@ export type UninstallAppMutationVariables = Exact<{
|
|||
|
||||
export type UninstallAppMutation = { __typename?: 'Mutation'; uninstallApp: { __typename: 'App'; id: string; status: AppStatusEnum } };
|
||||
|
||||
export type UpdateMutationVariables = Exact<{ [key: string]: never }>;
|
||||
|
||||
export type UpdateMutation = { __typename?: 'Mutation'; update: boolean };
|
||||
|
||||
export type UpdateAppMutationVariables = Exact<{
|
||||
id: Scalars['String'];
|
||||
}>;
|
||||
|
@ -398,22 +361,6 @@ export type RefreshTokenQueryVariables = Exact<{ [key: string]: never }>;
|
|||
|
||||
export type RefreshTokenQuery = { __typename?: 'Query'; refreshToken?: { __typename?: 'TokenResponse'; token: string } | null };
|
||||
|
||||
export type SystemInfoQueryVariables = Exact<{ [key: string]: never }>;
|
||||
|
||||
export type SystemInfoQuery = {
|
||||
__typename?: 'Query';
|
||||
systemInfo?: {
|
||||
__typename?: 'SystemInfoResponse';
|
||||
cpu: { __typename?: 'Cpu'; load: number };
|
||||
disk: { __typename?: 'DiskMemory'; available: number; used: number; total: number };
|
||||
memory: { __typename?: 'DiskMemory'; available: number; used: number; total: number };
|
||||
} | null;
|
||||
};
|
||||
|
||||
export type VersionQueryVariables = Exact<{ [key: string]: never }>;
|
||||
|
||||
export type VersionQuery = { __typename?: 'Query'; version: { __typename?: 'VersionResponse'; current: string; latest?: string | null } };
|
||||
|
||||
export const InstallAppDocument = gql`
|
||||
mutation InstallApp($input: AppInputType!) {
|
||||
installApp(input: $input) {
|
||||
|
@ -545,36 +492,6 @@ export function useRegisterMutation(baseOptions?: Apollo.MutationHookOptions<Reg
|
|||
export type RegisterMutationHookResult = ReturnType<typeof useRegisterMutation>;
|
||||
export type RegisterMutationResult = Apollo.MutationResult<RegisterMutation>;
|
||||
export type RegisterMutationOptions = Apollo.BaseMutationOptions<RegisterMutation, RegisterMutationVariables>;
|
||||
export const RestartDocument = gql`
|
||||
mutation Restart {
|
||||
restart
|
||||
}
|
||||
`;
|
||||
export type RestartMutationFn = Apollo.MutationFunction<RestartMutation, RestartMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useRestartMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useRestartMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useRestartMutation` returns a tuple that includes:
|
||||
* - A mutate function that you can call at any time to execute the mutation
|
||||
* - An object with fields that represent the current status of the mutation's execution
|
||||
*
|
||||
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||
*
|
||||
* @example
|
||||
* const [restartMutation, { data, loading, error }] = useRestartMutation({
|
||||
* variables: {
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useRestartMutation(baseOptions?: Apollo.MutationHookOptions<RestartMutation, RestartMutationVariables>) {
|
||||
const options = { ...defaultOptions, ...baseOptions };
|
||||
return Apollo.useMutation<RestartMutation, RestartMutationVariables>(RestartDocument, options);
|
||||
}
|
||||
export type RestartMutationHookResult = ReturnType<typeof useRestartMutation>;
|
||||
export type RestartMutationResult = Apollo.MutationResult<RestartMutation>;
|
||||
export type RestartMutationOptions = Apollo.BaseMutationOptions<RestartMutation, RestartMutationVariables>;
|
||||
export const StartAppDocument = gql`
|
||||
mutation StartApp($id: String!) {
|
||||
startApp(id: $id) {
|
||||
|
@ -680,36 +597,6 @@ export function useUninstallAppMutation(baseOptions?: Apollo.MutationHookOptions
|
|||
export type UninstallAppMutationHookResult = ReturnType<typeof useUninstallAppMutation>;
|
||||
export type UninstallAppMutationResult = Apollo.MutationResult<UninstallAppMutation>;
|
||||
export type UninstallAppMutationOptions = Apollo.BaseMutationOptions<UninstallAppMutation, UninstallAppMutationVariables>;
|
||||
export const UpdateDocument = gql`
|
||||
mutation Update {
|
||||
update
|
||||
}
|
||||
`;
|
||||
export type UpdateMutationFn = Apollo.MutationFunction<UpdateMutation, UpdateMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useUpdateMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useUpdateMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useUpdateMutation` returns a tuple that includes:
|
||||
* - A mutate function that you can call at any time to execute the mutation
|
||||
* - An object with fields that represent the current status of the mutation's execution
|
||||
*
|
||||
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||
*
|
||||
* @example
|
||||
* const [updateMutation, { data, loading, error }] = useUpdateMutation({
|
||||
* variables: {
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useUpdateMutation(baseOptions?: Apollo.MutationHookOptions<UpdateMutation, UpdateMutationVariables>) {
|
||||
const options = { ...defaultOptions, ...baseOptions };
|
||||
return Apollo.useMutation<UpdateMutation, UpdateMutationVariables>(UpdateDocument, options);
|
||||
}
|
||||
export type UpdateMutationHookResult = ReturnType<typeof useUpdateMutation>;
|
||||
export type UpdateMutationResult = Apollo.MutationResult<UpdateMutation>;
|
||||
export type UpdateMutationOptions = Apollo.BaseMutationOptions<UpdateMutation, UpdateMutationVariables>;
|
||||
export const UpdateAppDocument = gql`
|
||||
mutation UpdateApp($id: String!) {
|
||||
updateApp(id: $id) {
|
||||
|
@ -1048,84 +935,3 @@ export function useRefreshTokenLazyQuery(baseOptions?: Apollo.LazyQueryHookOptio
|
|||
export type RefreshTokenQueryHookResult = ReturnType<typeof useRefreshTokenQuery>;
|
||||
export type RefreshTokenLazyQueryHookResult = ReturnType<typeof useRefreshTokenLazyQuery>;
|
||||
export type RefreshTokenQueryResult = Apollo.QueryResult<RefreshTokenQuery, RefreshTokenQueryVariables>;
|
||||
export const SystemInfoDocument = gql`
|
||||
query SystemInfo {
|
||||
systemInfo {
|
||||
cpu {
|
||||
load
|
||||
}
|
||||
disk {
|
||||
available
|
||||
used
|
||||
total
|
||||
}
|
||||
memory {
|
||||
available
|
||||
used
|
||||
total
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useSystemInfoQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useSystemInfoQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useSystemInfoQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useSystemInfoQuery({
|
||||
* variables: {
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useSystemInfoQuery(baseOptions?: Apollo.QueryHookOptions<SystemInfoQuery, SystemInfoQueryVariables>) {
|
||||
const options = { ...defaultOptions, ...baseOptions };
|
||||
return Apollo.useQuery<SystemInfoQuery, SystemInfoQueryVariables>(SystemInfoDocument, options);
|
||||
}
|
||||
export function useSystemInfoLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<SystemInfoQuery, SystemInfoQueryVariables>) {
|
||||
const options = { ...defaultOptions, ...baseOptions };
|
||||
return Apollo.useLazyQuery<SystemInfoQuery, SystemInfoQueryVariables>(SystemInfoDocument, options);
|
||||
}
|
||||
export type SystemInfoQueryHookResult = ReturnType<typeof useSystemInfoQuery>;
|
||||
export type SystemInfoLazyQueryHookResult = ReturnType<typeof useSystemInfoLazyQuery>;
|
||||
export type SystemInfoQueryResult = Apollo.QueryResult<SystemInfoQuery, SystemInfoQueryVariables>;
|
||||
export const VersionDocument = gql`
|
||||
query Version {
|
||||
version {
|
||||
current
|
||||
latest
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useVersionQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useVersionQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useVersionQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useVersionQuery({
|
||||
* variables: {
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useVersionQuery(baseOptions?: Apollo.QueryHookOptions<VersionQuery, VersionQueryVariables>) {
|
||||
const options = { ...defaultOptions, ...baseOptions };
|
||||
return Apollo.useQuery<VersionQuery, VersionQueryVariables>(VersionDocument, options);
|
||||
}
|
||||
export function useVersionLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<VersionQuery, VersionQueryVariables>) {
|
||||
const options = { ...defaultOptions, ...baseOptions };
|
||||
return Apollo.useLazyQuery<VersionQuery, VersionQueryVariables>(VersionDocument, options);
|
||||
}
|
||||
export type VersionQueryHookResult = ReturnType<typeof useVersionQuery>;
|
||||
export type VersionLazyQueryHookResult = ReturnType<typeof useVersionLazyQuery>;
|
||||
export type VersionQueryResult = Apollo.QueryResult<VersionQuery, VersionQueryVariables>;
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
mutation Restart {
|
||||
restart
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
mutation Update {
|
||||
update
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
query SystemInfo {
|
||||
systemInfo {
|
||||
cpu {
|
||||
load
|
||||
}
|
||||
disk {
|
||||
available
|
||||
used
|
||||
total
|
||||
}
|
||||
memory {
|
||||
available
|
||||
used
|
||||
total
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
query Version {
|
||||
version {
|
||||
current
|
||||
latest
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { useEffect, useState, useCallback } from 'react';
|
||||
import { ApolloClient } from '@apollo/client';
|
||||
import { createApolloClient } from '../core/apollo/client';
|
||||
|
||||
|
@ -11,7 +11,7 @@ export default function useCachedResources(): IReturnProps {
|
|||
const [isLoadingComplete, setLoadingComplete] = useState(false);
|
||||
const [client, setClient] = useState<ApolloClient<unknown>>();
|
||||
|
||||
async function loadResourcesAndDataAsync() {
|
||||
const loadResourcesAndDataAsync = useCallback(() => {
|
||||
try {
|
||||
const restoredClient = createApolloClient();
|
||||
|
||||
|
@ -19,14 +19,18 @@ export default function useCachedResources(): IReturnProps {
|
|||
} catch (error) {
|
||||
// We might want to provide this error information to an error reporting service
|
||||
console.error(error);
|
||||
} finally {
|
||||
setLoadingComplete(true);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
loadResourcesAndDataAsync();
|
||||
}, []);
|
||||
}, [loadResourcesAndDataAsync]);
|
||||
|
||||
useEffect(() => {
|
||||
if (client) {
|
||||
setLoadingComplete(true);
|
||||
}
|
||||
}, [client]);
|
||||
|
||||
return { client, isLoadingComplete };
|
||||
}
|
||||
|
|
|
@ -1,239 +0,0 @@
|
|||
import { faker } from '@faker-js/faker';
|
||||
import axios from 'axios';
|
||||
import fs from 'fs-extra';
|
||||
import { DataSource } from 'typeorm';
|
||||
import TipiCache from '../../../config/TipiCache';
|
||||
import * as TipiConfig from '../../../core/config/TipiConfig';
|
||||
import { setConfig } from '../../../core/config/TipiConfig';
|
||||
import { setupConnection, teardownConnection } from '../../../test/connection';
|
||||
import { gcall } from '../../../test/gcall';
|
||||
import { restartMutation, updateMutation } from '../../../test/mutations';
|
||||
import { systemInfoQuery, versionQuery } from '../../../test/queries';
|
||||
import User from '../../auth/user.entity';
|
||||
import { createUser } from '../../auth/__tests__/user.factory';
|
||||
import { SystemInfoResponse } from '../system.types';
|
||||
import EventDispatcher from '../../../core/config/EventDispatcher';
|
||||
|
||||
jest.mock('fs-extra');
|
||||
jest.mock('axios');
|
||||
jest.mock('redis');
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.resetModules();
|
||||
jest.resetAllMocks();
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
let db: DataSource | null = null;
|
||||
const TEST_SUITE = 'systemresolver';
|
||||
beforeAll(async () => {
|
||||
db = await setupConnection(TEST_SUITE);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await db?.destroy();
|
||||
await teardownConnection(TEST_SUITE);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.resetModules();
|
||||
jest.resetAllMocks();
|
||||
jest.restoreAllMocks();
|
||||
await User.clear();
|
||||
});
|
||||
|
||||
describe('Test: systemInfo', () => {
|
||||
it('Should return correct system info from file', async () => {
|
||||
const systemInfo = {
|
||||
cpu: { load: 10 },
|
||||
memory: { available: 100, total: 1000, used: 900 },
|
||||
disk: { available: 100, total: 1000, used: 900 },
|
||||
};
|
||||
|
||||
const MockFiles = {
|
||||
'/runtipi/state/system-info.json': JSON.stringify(systemInfo),
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(MockFiles);
|
||||
|
||||
const { data } = await gcall<{ systemInfo: SystemInfoResponse }>({ source: systemInfoQuery });
|
||||
|
||||
expect(data?.systemInfo).toBeDefined();
|
||||
expect(data?.systemInfo.cpu).toBeDefined();
|
||||
expect(data?.systemInfo.cpu.load).toBe(systemInfo.cpu.load);
|
||||
expect(data?.systemInfo.memory).toBeDefined();
|
||||
expect(data?.systemInfo.memory.available).toBe(systemInfo.memory.available);
|
||||
expect(data?.systemInfo.memory.total).toBe(systemInfo.memory.total);
|
||||
expect(data?.systemInfo.memory.used).toBe(systemInfo.memory.used);
|
||||
expect(data?.systemInfo.disk).toBeDefined();
|
||||
expect(data?.systemInfo.disk.available).toBe(systemInfo.disk.available);
|
||||
expect(data?.systemInfo.disk.total).toBe(systemInfo.disk.total);
|
||||
expect(data?.systemInfo.disk.used).toBe(systemInfo.disk.used);
|
||||
});
|
||||
|
||||
it('Should return 0 for missing values', async () => {
|
||||
const systemInfo = {
|
||||
cpu: {},
|
||||
memory: {},
|
||||
disk: {},
|
||||
};
|
||||
|
||||
const MockFiles = {
|
||||
'/runtipi/state/system-info.json': JSON.stringify(systemInfo),
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(MockFiles);
|
||||
|
||||
const { data } = await gcall<{ systemInfo: SystemInfoResponse }>({ source: systemInfoQuery });
|
||||
|
||||
expect(data?.systemInfo).toBeDefined();
|
||||
expect(data?.systemInfo.cpu).toBeDefined();
|
||||
expect(data?.systemInfo.cpu.load).toBe(0);
|
||||
expect(data?.systemInfo.memory).toBeDefined();
|
||||
expect(data?.systemInfo.memory.available).toBe(0);
|
||||
expect(data?.systemInfo.memory.total).toBe(0);
|
||||
expect(data?.systemInfo.memory.used).toBe(0);
|
||||
expect(data?.systemInfo.disk).toBeDefined();
|
||||
expect(data?.systemInfo.disk.available).toBe(0);
|
||||
expect(data?.systemInfo.disk.total).toBe(0);
|
||||
expect(data?.systemInfo.disk.used).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test: getVersion', () => {
|
||||
const current = `${faker.random.numeric(1)}.${faker.random.numeric(1)}.${faker.random.numeric()}`;
|
||||
const latest = `${faker.random.numeric(1)}.${faker.random.numeric(1)}.${faker.random.numeric()}`;
|
||||
beforeEach(async () => {
|
||||
jest.spyOn(axios, 'get').mockResolvedValue({
|
||||
data: { name: `v${latest}` },
|
||||
});
|
||||
setConfig('version', current);
|
||||
});
|
||||
|
||||
it('Should return correct version', async () => {
|
||||
const { data } = await gcall<{ version: { current: string; latest?: string } }>({
|
||||
source: versionQuery,
|
||||
});
|
||||
|
||||
expect(data?.version).toBeDefined();
|
||||
expect(data?.version.current).toBeDefined();
|
||||
expect(data?.version.latest).toBeDefined();
|
||||
expect(data?.version.current).toBe(current);
|
||||
expect(data?.version.latest).toBe(latest);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test: restart', () => {
|
||||
beforeEach(async () => {
|
||||
setConfig('status', 'RUNNING');
|
||||
setConfig('version', '1.0.0');
|
||||
TipiCache.set('latestVersion', '1.0.1');
|
||||
});
|
||||
|
||||
it('Should return true', async () => {
|
||||
// Arrange
|
||||
EventDispatcher.prototype.dispatchEventAsync = jest.fn().mockResolvedValueOnce({ success: true });
|
||||
|
||||
// Act
|
||||
const user = await createUser();
|
||||
const { data } = await gcall<{ restart: boolean }>({ source: restartMutation, userId: user.id });
|
||||
|
||||
// Assert
|
||||
expect(data?.restart).toBeDefined();
|
||||
expect(data?.restart).toBe(true);
|
||||
});
|
||||
|
||||
it("Should return an error if user doesn't exist", async () => {
|
||||
// Arrange
|
||||
const { data, errors } = await gcall<{ restart: boolean }>({
|
||||
source: restartMutation,
|
||||
userId: 1,
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(errors?.[0].message).toBe('Access denied! You need to be authorized to perform this action!');
|
||||
expect(data?.restart).toBeUndefined();
|
||||
});
|
||||
|
||||
it('Should throw an error if no userId is not provided', async () => {
|
||||
// Arrange
|
||||
const { data, errors } = await gcall<{ restart: boolean }>({ source: restartMutation });
|
||||
|
||||
// Assert
|
||||
expect(errors?.[0].message).toBe('Access denied! You need to be authorized to perform this action!');
|
||||
expect(data?.restart).toBeUndefined();
|
||||
});
|
||||
|
||||
it('Should set app status to restarting', async () => {
|
||||
// Arrange
|
||||
EventDispatcher.prototype.dispatchEventAsync = jest.fn().mockResolvedValueOnce({ success: true });
|
||||
const spy = jest.spyOn(TipiConfig, 'setConfig');
|
||||
const user = await createUser();
|
||||
|
||||
// Act
|
||||
await gcall<{ restart: boolean }>({ source: restartMutation, userId: user.id });
|
||||
|
||||
// Assert
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
expect(spy).toHaveBeenNthCalledWith(1, 'status', 'RESTARTING');
|
||||
|
||||
spy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test: update', () => {
|
||||
beforeEach(async () => {
|
||||
setConfig('status', 'RUNNING');
|
||||
setConfig('version', '1.0.0');
|
||||
TipiCache.set('latestVersion', '1.0.1');
|
||||
});
|
||||
|
||||
it('Should return true', async () => {
|
||||
// Arrange
|
||||
EventDispatcher.prototype.dispatchEventAsync = jest.fn().mockResolvedValueOnce({ success: true });
|
||||
const user = await createUser();
|
||||
|
||||
// Act
|
||||
const { data } = await gcall<{ update: boolean }>({ source: updateMutation, userId: user.id });
|
||||
|
||||
// Assert
|
||||
expect(data?.update).toBeDefined();
|
||||
expect(data?.update).toBe(true);
|
||||
});
|
||||
|
||||
it("Should return an error if user doesn't exist", async () => {
|
||||
// Act
|
||||
const { data, errors } = await gcall<{ update: boolean }>({ source: updateMutation, userId: 1 });
|
||||
|
||||
// Assert
|
||||
expect(errors?.[0].message).toBe('Access denied! You need to be authorized to perform this action!');
|
||||
expect(data?.update).toBeUndefined();
|
||||
});
|
||||
|
||||
it('Should throw an error if no userId is not provided', async () => {
|
||||
// Act
|
||||
const { data, errors } = await gcall<{ update: boolean }>({ source: updateMutation });
|
||||
|
||||
// Assert
|
||||
expect(errors?.[0].message).toBe('Access denied! You need to be authorized to perform this action!');
|
||||
expect(data?.update).toBeUndefined();
|
||||
});
|
||||
|
||||
it('Should set app status to updating', async () => {
|
||||
// Arrange
|
||||
EventDispatcher.prototype.dispatchEventAsync = jest.fn().mockResolvedValueOnce({ success: true });
|
||||
const spy = jest.spyOn(TipiConfig, 'setConfig');
|
||||
const user = await createUser();
|
||||
|
||||
// Act
|
||||
await gcall<{ update: boolean }>({ source: updateMutation, userId: user.id });
|
||||
|
||||
// Assert
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
expect(spy).toHaveBeenNthCalledWith(1, 'status', 'UPDATING');
|
||||
|
||||
spy.mockRestore();
|
||||
});
|
||||
});
|
|
@ -1,187 +0,0 @@
|
|||
import fs from 'fs-extra';
|
||||
import semver from 'semver';
|
||||
import axios from 'axios';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import SystemService from '../system.service';
|
||||
import TipiCache from '../../../config/TipiCache';
|
||||
import { setConfig } from '../../../core/config/TipiConfig';
|
||||
import EventDispatcher from '../../../core/config/EventDispatcher';
|
||||
|
||||
jest.mock('fs-extra');
|
||||
jest.mock('axios');
|
||||
jest.mock('redis');
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.resetModules();
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('Test: systemInfo', () => {
|
||||
it('Should throw if system-info.json does not exist', () => {
|
||||
try {
|
||||
SystemService.systemInfo();
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof Error) {
|
||||
expect(e).toBeDefined();
|
||||
expect(e.message).toBe('Error parsing system info');
|
||||
} else {
|
||||
fail('Should throw an error');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('It should return system info', async () => {
|
||||
// Arrange
|
||||
const info = {
|
||||
cpu: { load: 0.1 },
|
||||
memory: { available: 1000, total: 2000, used: 1000 },
|
||||
disk: { available: 1000, total: 2000, used: 1000 },
|
||||
};
|
||||
|
||||
const MockFiles = {
|
||||
'/runtipi/state/system-info.json': JSON.stringify(info),
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(MockFiles);
|
||||
|
||||
// Act
|
||||
const systemInfo = SystemService.systemInfo();
|
||||
|
||||
// Assert
|
||||
expect(systemInfo).toBeDefined();
|
||||
expect(systemInfo.cpu).toBeDefined();
|
||||
expect(systemInfo.memory).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test: getVersion', () => {
|
||||
beforeEach(() => {
|
||||
TipiCache.del('latestVersion');
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('It should return version', async () => {
|
||||
// Arrange
|
||||
const spy = jest.spyOn(axios, 'get').mockResolvedValue({
|
||||
data: { name: `v${faker.random.numeric(1)}.${faker.random.numeric(1)}.${faker.random.numeric()}` },
|
||||
});
|
||||
|
||||
// Act
|
||||
const version = await SystemService.getVersion();
|
||||
|
||||
// Assert
|
||||
expect(version).toBeDefined();
|
||||
expect(version.current).toBeDefined();
|
||||
expect(semver.valid(version.latest)).toBeTruthy();
|
||||
|
||||
spy.mockRestore();
|
||||
});
|
||||
|
||||
it('Should return undefined for latest if request fails', async () => {
|
||||
jest.spyOn(axios, 'get').mockImplementation(() => {
|
||||
throw new Error('Error');
|
||||
});
|
||||
|
||||
const version = await SystemService.getVersion();
|
||||
|
||||
expect(version).toBeDefined();
|
||||
expect(version.current).toBeDefined();
|
||||
expect(version.latest).toBeUndefined();
|
||||
});
|
||||
|
||||
it('Should return cached version', async () => {
|
||||
// Arrange
|
||||
const spy = jest.spyOn(axios, 'get').mockResolvedValue({
|
||||
data: { name: `v${faker.random.numeric(1)}.${faker.random.numeric(1)}.${faker.random.numeric()}` },
|
||||
});
|
||||
|
||||
// Act
|
||||
const version = await SystemService.getVersion();
|
||||
const version2 = await SystemService.getVersion();
|
||||
|
||||
// Assert
|
||||
expect(version).toBeDefined();
|
||||
expect(version.current).toBeDefined();
|
||||
expect(semver.valid(version.latest)).toBeTruthy();
|
||||
|
||||
expect(version2.latest).toBe(version.latest);
|
||||
expect(version2.current).toBeDefined();
|
||||
expect(semver.valid(version2.latest)).toBeTruthy();
|
||||
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
|
||||
spy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test: restart', () => {
|
||||
it('Should return true', async () => {
|
||||
// Arrange
|
||||
EventDispatcher.prototype.dispatchEventAsync = jest.fn().mockResolvedValueOnce({ success: true });
|
||||
|
||||
// Act
|
||||
const restart = await SystemService.restart();
|
||||
|
||||
// Assert
|
||||
expect(restart).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test: update', () => {
|
||||
it('Should return true', async () => {
|
||||
// Arrange
|
||||
EventDispatcher.prototype.dispatchEventAsync = jest.fn().mockResolvedValueOnce({ success: true });
|
||||
setConfig('version', '0.0.1');
|
||||
TipiCache.set('latestVersion', '0.0.2');
|
||||
|
||||
// Act
|
||||
const update = await SystemService.update();
|
||||
|
||||
// Assert
|
||||
expect(update).toBeTruthy();
|
||||
});
|
||||
|
||||
it('Should throw an error if latest version is not set', async () => {
|
||||
// Arrange
|
||||
TipiCache.del('latestVersion');
|
||||
const spy = jest.spyOn(axios, 'get').mockResolvedValue({
|
||||
data: { name: null },
|
||||
});
|
||||
setConfig('version', '0.0.1');
|
||||
|
||||
// Act & Assert
|
||||
await expect(SystemService.update()).rejects.toThrow('Could not get latest version');
|
||||
spy.mockRestore();
|
||||
});
|
||||
|
||||
it('Should throw if current version is higher than latest', async () => {
|
||||
// Arrange
|
||||
setConfig('version', '0.0.2');
|
||||
TipiCache.set('latestVersion', '0.0.1');
|
||||
|
||||
// Act & Assert
|
||||
await expect(SystemService.update()).rejects.toThrow('Current version is newer than latest version');
|
||||
});
|
||||
|
||||
it('Should throw if current version is equal to latest', async () => {
|
||||
// Arrange
|
||||
setConfig('version', '0.0.1');
|
||||
TipiCache.set('latestVersion', '0.0.1');
|
||||
|
||||
// Act & Assert
|
||||
await expect(SystemService.update()).rejects.toThrow('Current version is already up to date');
|
||||
});
|
||||
|
||||
it('Should throw an error if there is a major version difference', async () => {
|
||||
// Arrange
|
||||
setConfig('version', '0.0.1');
|
||||
TipiCache.set('latestVersion', '1.0.0');
|
||||
|
||||
// Act & Assert
|
||||
await expect(SystemService.update()).rejects.toThrow('The major version has changed. Please update manually');
|
||||
});
|
||||
});
|
|
@ -1,12 +0,0 @@
|
|||
import { Request, Response } from 'express';
|
||||
import { getConfig } from '../../core/config/TipiConfig';
|
||||
|
||||
const status = async (req: Request, res: Response) => {
|
||||
res.status(200).json({
|
||||
status: getConfig().status,
|
||||
});
|
||||
};
|
||||
|
||||
export default {
|
||||
status,
|
||||
};
|
|
@ -1,28 +0,0 @@
|
|||
import { Authorized, Mutation, Query, Resolver } from 'type-graphql';
|
||||
import SystemService from './system.service';
|
||||
import { SystemInfoResponse, VersionResponse } from './system.types';
|
||||
|
||||
@Resolver()
|
||||
export default class AuthResolver {
|
||||
@Query(() => SystemInfoResponse, { nullable: true })
|
||||
async systemInfo(): Promise<SystemInfoResponse> {
|
||||
return SystemService.systemInfo();
|
||||
}
|
||||
|
||||
@Query(() => VersionResponse)
|
||||
async version(): Promise<VersionResponse> {
|
||||
return SystemService.getVersion();
|
||||
}
|
||||
|
||||
@Authorized()
|
||||
@Mutation(() => Boolean)
|
||||
async restart(): Promise<boolean> {
|
||||
return SystemService.restart();
|
||||
}
|
||||
|
||||
@Authorized()
|
||||
@Mutation(() => Boolean)
|
||||
async update(): Promise<boolean> {
|
||||
return SystemService.update();
|
||||
}
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
import axios from 'axios';
|
||||
import z from 'zod';
|
||||
import semver from 'semver';
|
||||
import logger from '../../config/logger/logger';
|
||||
import TipiCache from '../../config/TipiCache';
|
||||
import { getConfig, setConfig } from '../../core/config/TipiConfig';
|
||||
import { readJsonFile } from '../fs/fs.helpers';
|
||||
import { eventDispatcher, EventTypes } from '../../core/config/EventDispatcher';
|
||||
|
||||
const systemInfoSchema = z.object({
|
||||
cpu: z.object({
|
||||
load: z.number().default(0),
|
||||
}),
|
||||
disk: z.object({
|
||||
total: z.number().default(0),
|
||||
used: z.number().default(0),
|
||||
available: z.number().default(0),
|
||||
}),
|
||||
memory: z.object({
|
||||
total: z.number().default(0),
|
||||
available: z.number().default(0),
|
||||
used: z.number().default(0),
|
||||
}),
|
||||
});
|
||||
|
||||
const systemInfo = (): z.infer<typeof systemInfoSchema> => {
|
||||
const info = systemInfoSchema.safeParse(readJsonFile('/runtipi/state/system-info.json'));
|
||||
|
||||
if (!info.success) {
|
||||
logger.error('Error parsing system info');
|
||||
logger.error(info.error);
|
||||
throw new Error('Error parsing system info');
|
||||
} else {
|
||||
return info.data;
|
||||
}
|
||||
};
|
||||
|
||||
const getVersion = async (): Promise<{ current: string; latest?: string }> => {
|
||||
try {
|
||||
let version = await TipiCache.get('latestVersion');
|
||||
|
||||
if (!version) {
|
||||
const { data } = await axios.get('https://api.github.com/repos/meienberger/runtipi/releases/latest');
|
||||
|
||||
version = data.name.replace('v', '');
|
||||
await TipiCache.set('latestVersion', version?.replace('v', '') || '', 60 * 60);
|
||||
}
|
||||
|
||||
return { current: getConfig().version, latest: version?.replace('v', '') };
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
return { current: getConfig().version, latest: undefined };
|
||||
}
|
||||
};
|
||||
|
||||
const restart = async (): Promise<boolean> => {
|
||||
setConfig('status', 'RESTARTING');
|
||||
|
||||
eventDispatcher.dispatchEventAsync(EventTypes.RESTART);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const update = async (): Promise<boolean> => {
|
||||
const { current, latest } = await getVersion();
|
||||
|
||||
if (!latest) {
|
||||
throw new Error('Could not get latest version');
|
||||
}
|
||||
|
||||
if (semver.gt(current, latest)) {
|
||||
throw new Error('Current version is newer than latest version');
|
||||
}
|
||||
|
||||
if (semver.eq(current, latest)) {
|
||||
throw new Error('Current version is already up to date');
|
||||
}
|
||||
|
||||
if (semver.major(current) !== semver.major(latest)) {
|
||||
throw new Error('The major version has changed. Please update manually');
|
||||
}
|
||||
|
||||
setConfig('status', 'UPDATING');
|
||||
|
||||
eventDispatcher.dispatchEventAsync(EventTypes.UPDATE);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const SystemService = {
|
||||
systemInfo,
|
||||
getVersion,
|
||||
restart,
|
||||
update,
|
||||
};
|
||||
|
||||
export default SystemService;
|
|
@ -1,42 +0,0 @@
|
|||
import { Field, ObjectType } from 'type-graphql';
|
||||
|
||||
@ObjectType()
|
||||
class Cpu {
|
||||
@Field(() => Number, { nullable: false })
|
||||
load!: number;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
class DiskMemory {
|
||||
@Field(() => Number, { nullable: false })
|
||||
total!: number;
|
||||
|
||||
@Field(() => Number, { nullable: false })
|
||||
used!: number;
|
||||
|
||||
@Field(() => Number, { nullable: false })
|
||||
available!: number;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
class SystemInfoResponse {
|
||||
@Field(() => Cpu, { nullable: false })
|
||||
cpu!: Cpu;
|
||||
|
||||
@Field(() => DiskMemory, { nullable: false })
|
||||
disk!: DiskMemory;
|
||||
|
||||
@Field(() => DiskMemory, { nullable: false })
|
||||
memory!: DiskMemory;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
class VersionResponse {
|
||||
@Field(() => String, { nullable: false })
|
||||
current!: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
latest?: string;
|
||||
}
|
||||
|
||||
export { SystemInfoResponse, VersionResponse };
|
|
@ -3,11 +3,10 @@ import { buildSchema } from 'type-graphql';
|
|||
import { customAuthChecker } from './core/middlewares/authChecker';
|
||||
import AppsResolver from './modules/apps/apps.resolver';
|
||||
import AuthResolver from './modules/auth/auth.resolver';
|
||||
import SystemResolver from './modules/system/system.resolver';
|
||||
|
||||
const createSchema = (): Promise<GraphQLSchema> =>
|
||||
buildSchema({
|
||||
resolvers: [AppsResolver, AuthResolver, SystemResolver],
|
||||
resolvers: [AppsResolver, AuthResolver],
|
||||
validate: true,
|
||||
authChecker: customAuthChecker,
|
||||
});
|
||||
|
|
|
@ -17,7 +17,6 @@ import { runUpdates } from './core/updates/run';
|
|||
import recover from './core/updates/recover-migrations';
|
||||
import startJobs from './core/jobs/jobs';
|
||||
import { applyJsonConfig, getConfig, setConfig } from './core/config/TipiConfig';
|
||||
import systemController from './modules/system/system.controller';
|
||||
import { eventDispatcher, EventTypes } from './core/config/EventDispatcher';
|
||||
|
||||
const applyCustomConfig = () => {
|
||||
|
@ -50,7 +49,6 @@ const main = async () => {
|
|||
|
||||
app.use(cors(corsOptions));
|
||||
app.use(express.static(`${getConfig().rootFolder}/repos/${getConfig().appsRepoId}`));
|
||||
app.use('/status', systemController.status);
|
||||
app.use(getSessionMiddleware);
|
||||
|
||||
await datasource.initialize();
|
||||
|
|
|
@ -10,8 +10,6 @@ import * as updateAppConfig from './updateAppConfig.graphql';
|
|||
import * as updateApp from './updateApp.graphql';
|
||||
import * as register from './register.graphql';
|
||||
import * as login from './login.graphql';
|
||||
import * as restart from './restart.graphql';
|
||||
import * as update from './update.graphql';
|
||||
|
||||
export const installAppMutation = print(installApp);
|
||||
export const startAppMutation = print(startApp);
|
||||
|
@ -21,5 +19,3 @@ export const updateAppConfigMutation = print(updateAppConfig);
|
|||
export const updateAppMutation = print(updateApp);
|
||||
export const registerMutation = print(register);
|
||||
export const loginMutation = print(login);
|
||||
export const restartMutation = print(restart);
|
||||
export const updateMutation = print(update);
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
mutation {
|
||||
restart
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
mutation {
|
||||
update
|
||||
}
|
|
@ -7,8 +7,6 @@ import * as getApp from './getApp.graphql';
|
|||
import * as InstalledApps from './installedApps.graphql';
|
||||
import * as Me from './me.graphql';
|
||||
import * as isConfigured from './isConfigured.graphql';
|
||||
import * as systemInfo from './systemInfo.graphql';
|
||||
import * as version from './version.graphql';
|
||||
import * as refreshToken from './refreshToken.graphql';
|
||||
|
||||
export const listAppInfosQuery = print(listAppInfos);
|
||||
|
@ -16,6 +14,4 @@ export const getAppQuery = print(getApp);
|
|||
export const InstalledAppsQuery = print(InstalledApps);
|
||||
export const MeQuery = print(Me);
|
||||
export const isConfiguredQuery = print(isConfigured);
|
||||
export const systemInfoQuery = print(systemInfo);
|
||||
export const versionQuery = print(version);
|
||||
export const refreshTokenQuery = print(refreshToken);
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
query {
|
||||
systemInfo {
|
||||
cpu {
|
||||
load
|
||||
}
|
||||
memory {
|
||||
total
|
||||
available
|
||||
used
|
||||
}
|
||||
disk {
|
||||
total
|
||||
available
|
||||
used
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue