refactor: use text as default field type to avoid breaking when future field types are added

This commit is contained in:
Nicolas Meienberger 2023-04-15 02:03:22 +02:00 committed by Nicolas Meienberger
parent f35bdb7611
commit d3bd0b0cf9
4 changed files with 56 additions and 14 deletions

View file

@ -1,8 +1,9 @@
import fs from 'fs-extra';
import { fromAny } from '@total-typescript/shoehorn';
import { App, PrismaClient } from '@prisma/client';
import { faker } from '@faker-js/faker';
import { setConfig } from '../../core/TipiConfig';
import { AppInfo, checkAppRequirements, checkEnvFile, ensureAppFolder, generateEnvFile, getAppInfo, getAvailableApps, getEnvMap, getUpdateInfo } from './apps.helpers';
import { AppInfo, appInfoSchema, checkAppRequirements, checkEnvFile, ensureAppFolder, generateEnvFile, getAppInfo, getAvailableApps, getEnvMap, getUpdateInfo } from './apps.helpers';
import { createApp, createAppConfig } from '../../tests/apps.factory';
import { Logger } from '../../core/Logger';
import { getTestDbClient } from '../../../../tests/server/db-connection';
@ -136,6 +137,41 @@ describe('Test: checkEnvFile', () => {
});
});
describe('Test: appInfoSchema', () => {
it('should default form_field type to text if it is wrong', async () => {
// arrange
const config = createAppConfig(fromAny({ form_fields: [{ env_variable: 'test', type: 'wrong', label: 'yo', required: true }] }));
fs.writeFileSync(`/app/storage/app-data/${config.id}/config.json`, JSON.stringify(config));
// act
const appInfo = appInfoSchema.safeParse(config);
// assert
expect(appInfo.success).toBe(true);
if (appInfo.success) {
expect(appInfo.data.form_fields[0]?.type).toBe('text');
} else {
expect(true).toBe(false);
}
});
it('should default categories to ["utilities"] if it is wrong', async () => {
// arrange
const config = createAppConfig(fromAny({ categories: 'wrong' }));
fs.writeFileSync(`/app/storage/app-data/${config.id}/config.json`, JSON.stringify(config));
// act
const appInfo = appInfoSchema.safeParse(config);
// assert
expect(appInfo.success).toBe(true);
if (appInfo.success) {
expect(appInfo.data.categories).toStrictEqual(['utilities']);
} else {
expect(true).toBe(false);
}
});
});
describe('Test: generateEnvFile', () => {
let app1: AppInfo;
let appEntity1: App;

View file

@ -10,13 +10,17 @@ import { notEmpty } from '../../common/typescript.helpers';
import { ARCHITECTURES } from '../../core/TipiConfig/TipiConfig';
const formFieldSchema = z.object({
type: z.nativeEnum(FIELD_TYPES),
type: z.nativeEnum(FIELD_TYPES).catch(() => FIELD_TYPES.TEXT),
label: z.string(),
placeholder: z.string().optional(),
max: z.number().optional(),
min: z.number().optional(),
hint: z.string().optional(),
options: z.object({ label: z.string(), value: z.string() }).array().optional(),
required: z.boolean().optional().default(false),
default: z.union([z.boolean(), z.string()]).optional(),
regex: z.string().optional(),
pattern_error: z.string().optional(),
env_variable: z.string(),
});
@ -32,7 +36,13 @@ export const appInfoSchema = z.object({
author: z.string(),
source: z.string(),
website: z.string().optional(),
categories: z.nativeEnum(APP_CATEGORIES).array(),
categories: z
.nativeEnum(APP_CATEGORIES)
.array()
.catch((ctx) => {
Logger.warn(`Invalid categories "${JSON.stringify(ctx.input)}" defaulting to utilities`);
return [APP_CATEGORIES.UTILITIES];
}),
url_suffix: z.string().optional(),
form_fields: z.array(formFieldSchema).optional().default([]),
https: z.boolean().optional().default(false),

View file

@ -2,7 +2,7 @@ import { faker } from '@faker-js/faker';
import { App, PrismaClient } from '@prisma/client';
import { Architecture } from '../core/TipiConfig/TipiConfig';
import { AppInfo, appInfoSchema } from '../services/apps/apps.helpers';
import { AppCategory, APP_CATEGORIES } from '../services/apps/apps.types';
import { APP_CATEGORIES } from '../services/apps/apps.types';
interface IProps {
installed?: boolean;
@ -15,24 +15,19 @@ interface IProps {
supportedArchitectures?: Architecture[];
}
type CreateConfigParams = {
id?: string;
name?: string;
categories?: AppCategory[];
};
const createAppConfig = (props?: CreateConfigParams) =>
const createAppConfig = (props?: Partial<AppInfo>) =>
appInfoSchema.parse({
id: props?.id || faker.random.alphaNumeric(32),
id: faker.random.alphaNumeric(32),
available: true,
port: faker.datatype.number({ min: 30, max: 65535 }),
name: props?.name || faker.random.alphaNumeric(32),
name: faker.random.alphaNumeric(32),
description: faker.random.alphaNumeric(32),
tipi_version: 1,
short_desc: faker.random.alphaNumeric(32),
author: faker.random.alphaNumeric(32),
source: faker.internet.url(),
categories: props?.categories || [APP_CATEGORIES.AUTOMATION],
categories: [APP_CATEGORIES.AUTOMATION],
...props,
});
const createApp = async (props: IProps, db?: PrismaClient) => {

View file

@ -8,6 +8,7 @@ jest.mock('../../src/server/core/Logger', () => ({
Logger: {
info: jest.fn(),
error: jest.fn(),
warn: jest.fn(),
},
}));