 1cc62c2202
			
		
	
	
		1cc62c2202
		
			
		
	
	
	
	
		
			
			* Add the extra config to the first 50 apps and modify tests, * Add arch to extra 55 apps. * Fix tests error. * Fix tests (again). * Andd the last 77 apps. * Change photoprism from latest to a version. * Fixes. * Drop arm support for peppermint. --------- Co-authored-by: JigSaw <JigSawFr@users.noreply.github.com>
		
			
				
	
	
		
			286 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			286 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import fs from "fs";
 | |
| import jsyaml from "js-yaml";
 | |
| 
 | |
| type FormField = {
 | |
|   type: "random";
 | |
|   required: boolean;
 | |
| };
 | |
| 
 | |
| interface AppConfig {
 | |
|   id: string;
 | |
|   port: number;
 | |
|   categories: string[];
 | |
|   requirements?: {
 | |
|     ports?: number[];
 | |
|   };
 | |
|   name: string;
 | |
|   description: string;
 | |
|   version?: string;
 | |
|   tipi_version: number;
 | |
|   short_desc: string;
 | |
|   author: string;
 | |
|   source: string;
 | |
|   available: boolean;
 | |
|   form_fields?: FormField[];
 | |
|   supported_architectures: string[];
 | |
| }
 | |
| 
 | |
| const networkExceptions = [
 | |
|   "pihole",
 | |
|   "tailscale",
 | |
|   "homeassistant",
 | |
|   "plex",
 | |
|   "zerotier",
 | |
|   "gladys",
 | |
|   "scrypted",
 | |
|   "homebridge",
 | |
|   "cloudflared",
 | |
| ];
 | |
| const getAppConfigs = (): AppConfig[] => {
 | |
|   const apps: AppConfig[] = [];
 | |
| 
 | |
|   const appsDir = fs.readdirSync("./apps");
 | |
| 
 | |
|   appsDir.forEach((app: string) => {
 | |
|     const path = `./apps/${app}/config.json`;
 | |
| 
 | |
|     if (fs.existsSync(path)) {
 | |
|       const configFile = fs.readFileSync(path).toString();
 | |
| 
 | |
|       try {
 | |
|         const config: AppConfig = JSON.parse(configFile);
 | |
|         if (config.available) {
 | |
|           apps.push(config);
 | |
|         }
 | |
|       } catch (e) {
 | |
|         console.error("Error parsing config file", app);
 | |
|       }
 | |
|     }
 | |
|   });
 | |
| 
 | |
|   return apps;
 | |
| };
 | |
| 
 | |
| describe("App configs", () => {
 | |
|   it("Get app config should return at least one app", () => {
 | |
|     const apps = getAppConfigs();
 | |
| 
 | |
|     expect(apps.length).toBeGreaterThan(0);
 | |
|   });
 | |
| 
 | |
|   describe("Each app should have an id", () => {
 | |
|     const apps = getAppConfigs();
 | |
| 
 | |
|     apps.forEach((app) => {
 | |
|       test(app.id, () => {
 | |
|         expect(app.id).toBeDefined();
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe("Each app should have a md description", () => {
 | |
|     const apps = getAppConfigs();
 | |
| 
 | |
|     apps.forEach((app) => {
 | |
|       test(app.id, () => {
 | |
|         const path = `./apps/${app.id}/metadata/description.md`;
 | |
| 
 | |
|         if (fs.existsSync(path)) {
 | |
|           const description = fs.readFileSync(path).toString();
 | |
|           expect(description).toBeDefined();
 | |
|         } else {
 | |
|           expect(true).toBe(false);
 | |
|         }
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe("Each app should have categories defined as an array", () => {
 | |
|     const apps = getAppConfigs();
 | |
| 
 | |
|     apps.forEach((app) => {
 | |
|       test(app.id, () => {
 | |
|         expect(app.categories).toBeDefined();
 | |
|         expect(app.categories).toBeInstanceOf(Array);
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe("Each app should have a name", () => {
 | |
|     const apps = getAppConfigs();
 | |
| 
 | |
|     apps.forEach((app) => {
 | |
|       test(app.id, () => {
 | |
|         expect(app.name).toBeDefined();
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe("Each app should have a description", () => {
 | |
|     const apps = getAppConfigs();
 | |
| 
 | |
|     apps.forEach((app) => {
 | |
|       test(app.id, () => {
 | |
|         expect(app.description).toBeDefined();
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe("Each app should have a port", () => {
 | |
|     const apps = getAppConfigs();
 | |
| 
 | |
|     apps.forEach((app) => {
 | |
|       test(app.id, () => {
 | |
|         expect(app.port).toBeDefined();
 | |
|         expect(app.port).toBeGreaterThan(999);
 | |
|         expect(app.port).toBeLessThan(65535);
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe("Each app should have a supported architecture", () => {
 | |
|     const apps = getAppConfigs();
 | |
| 
 | |
|     apps.forEach((app) => {
 | |
|       test(app.id, () => {
 | |
|         expect(app.supported_architectures).toBeDefined();
 | |
|         expect(app.supported_architectures).toBeInstanceOf(Array);
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   test("Each app should have a different port", () => {
 | |
|     const appConfigs = getAppConfigs();
 | |
|     const ports = appConfigs.map((app) => app.port);
 | |
|     expect(new Set(ports).size).toBe(appConfigs.length);
 | |
|   });
 | |
| 
 | |
|   test("Each app should have a unique id", () => {
 | |
|     const appConfigs = getAppConfigs();
 | |
|     const ids = appConfigs.map((app) => app.id);
 | |
|     expect(new Set(ids).size).toBe(appConfigs.length);
 | |
|   });
 | |
| 
 | |
|   describe("Each app should have a version", () => {
 | |
|     const apps = getAppConfigs();
 | |
| 
 | |
|     apps.forEach((app) => {
 | |
|       test(app.id, () => {
 | |
|         expect(app.version).toBeDefined();
 | |
|         expect(app.tipi_version).toBeDefined();
 | |
|         expect(app.tipi_version).toBeGreaterThan(0);
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe("Each app should have a docker-compose file beside it", () => {
 | |
|     const apps = getAppConfigs();
 | |
| 
 | |
|     apps.forEach((app) => {
 | |
|       test(app.id, () => {
 | |
|         expect(fs.existsSync(`./apps/${app.id}/docker-compose.yml`)).toBe(true);
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe("Each app should have a metadata folder beside it", () => {
 | |
|     const apps = getAppConfigs();
 | |
| 
 | |
|     apps.forEach((app) => {
 | |
|       test(app.id, () => {
 | |
|         expect(fs.existsSync(`./apps/${app.id}/metadata`)).toBe(true);
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe("Each app should have a file named logo.jpg in the metadata folder", () => {
 | |
|     const apps = getAppConfigs();
 | |
| 
 | |
|     apps.forEach((app) => {
 | |
|       test(app.id, () => {
 | |
|         expect(fs.existsSync(`./apps/${app.id}/metadata/logo.jpg`)).toBe(true);
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe("Each app should have a container name equals to its id", () => {
 | |
|     const apps = getAppConfigs();
 | |
| 
 | |
|     apps.forEach((app) => {
 | |
|       test(app.id, () => {
 | |
|         const dockerComposeFile = fs
 | |
|           .readFileSync(`./apps/${app.id}/docker-compose.yml`)
 | |
|           .toString();
 | |
| 
 | |
|         const dockerCompose: any = jsyaml.load(dockerComposeFile);
 | |
| 
 | |
|         expect(dockerCompose.services[app.id]).toBeDefined();
 | |
|         expect(dockerCompose.services[app.id].container_name).toBe(app.id);
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe("Each app should have the same version in config.json and docker-compose.yml", () => {
 | |
|     const exceptions = ["revolt"];
 | |
|     const apps = getAppConfigs().filter((app) => !exceptions.includes(app.id));
 | |
| 
 | |
|     apps.forEach((app) => {
 | |
|       test(app.id, () => {
 | |
|         const dockerComposeFile = fs
 | |
|           .readFileSync(`./apps/${app.id}/docker-compose.yml`)
 | |
|           .toString();
 | |
| 
 | |
|         const dockerCompose: any = jsyaml.load(dockerComposeFile);
 | |
| 
 | |
|         expect(dockerCompose.services[app.id]).toBeDefined();
 | |
|         expect(dockerCompose.services[app.id].image).toBeDefined();
 | |
| 
 | |
|         const dockerImage = dockerCompose.services[app.id].image;
 | |
| 
 | |
|         const version = dockerImage.split(":")[1];
 | |
| 
 | |
|         expect(version).toContain(app.version);
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe("Each app should have network tipi_main_network", () => {
 | |
|     const apps = getAppConfigs();
 | |
| 
 | |
|     apps.forEach((app) => {
 | |
|       test(app.id, () => {
 | |
|         if (!networkExceptions.includes(app.id)) {
 | |
|           const dockerComposeFile = fs
 | |
|             .readFileSync(`./apps/${app.id}/docker-compose.yml`)
 | |
|             .toString();
 | |
| 
 | |
|           const dockerCompose: any = jsyaml.load(dockerComposeFile);
 | |
| 
 | |
|           expect(dockerCompose.services[app.id]).toBeDefined();
 | |
| 
 | |
|           expect(dockerCompose.services[app.id].networks).toBeDefined();
 | |
|           expect(dockerCompose.services[app.id].networks).toStrictEqual([
 | |
|             "tipi_main_network",
 | |
|           ]);
 | |
|         }
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe("All form fields with type random should not be marked as required", () => {
 | |
|     const configs = getAppConfigs();
 | |
|     configs.forEach((config) => {
 | |
|       const formFields = config.form_fields;
 | |
|       if (formFields) {
 | |
|         formFields.forEach((field) => {
 | |
|           if (field.type === "random") {
 | |
|             test(config.id, () => {
 | |
|               expect(Boolean(field.required)).toBe(false);
 | |
|             });
 | |
|           }
 | |
|         });
 | |
|       }
 | |
|     });
 | |
|   });
 | |
| });
 |