fix(custom-widgets): harden v2 authoring
This commit is contained in:
171 files changed
+20311
-50527
No files matched your search
@@ -0,0 +1,64 @@
|
||||
import { parse } from "superjson";
|
||||
import { expect } from "vitest";
|
||||
|
||||
import { BUNDLED_CUSTOM_WIDGETS, customWidgetDefinitionSchema } from "@homarr/custom-widgets/core";
|
||||
|
||||
import type { Database } from "..";
|
||||
import { eq } from "..";
|
||||
import { seedDataAsync } from "../migrations/seed";
|
||||
import { customWidgetDefinitions, customWidgetSecrets } from "../schema";
|
||||
|
||||
const expectedSeedIds = BUNDLED_CUSTOM_WIDGETS.map(({ id }) => id).toSorted();
|
||||
|
||||
const reconstructWidget = (definition: typeof customWidgetDefinitions.$inferSelect) =>
|
||||
customWidgetDefinitionSchema.parse({
|
||||
$schema: "homarr-custom-widget-v2",
|
||||
name: definition.name,
|
||||
...(definition.description === null ? {} : { description: definition.description }),
|
||||
...(definition.iconUrl === null ? {} : { iconUrl: definition.iconUrl }),
|
||||
sources: parse(definition.sources),
|
||||
requests: parse(definition.requests),
|
||||
optionsSchema: parse(definition.optionsSchema),
|
||||
defaultOptions: parse(definition.defaultOptions),
|
||||
template: definition.template,
|
||||
});
|
||||
|
||||
export const expectBundledCustomWidgetsSeeded = async (db: Database) => {
|
||||
const definitions = await db.query.customWidgetDefinitions.findMany({
|
||||
where: (table, { like }) => like(table.id, "seed-%"),
|
||||
});
|
||||
|
||||
expect(definitions.map(({ id }) => id).toSorted()).toEqual(expectedSeedIds);
|
||||
expect(definitions.every(({ enabled }) => !enabled)).toBe(true);
|
||||
expect(definitions.every(({ creatorId }) => creatorId === null)).toBe(true);
|
||||
expect(await db.$count(customWidgetSecrets)).toBe(0);
|
||||
|
||||
for (const definition of definitions) {
|
||||
const expected = BUNDLED_CUSTOM_WIDGETS.find(({ id }) => id === definition.id);
|
||||
expect(expected).toBeDefined();
|
||||
expect(reconstructWidget(definition)).toEqual(expected?.widget);
|
||||
}
|
||||
|
||||
await db
|
||||
.update(customWidgetDefinitions)
|
||||
.set({ name: "User-edited dog facts", enabled: true })
|
||||
.where(eq(customWidgetDefinitions.id, "seed-dog-facts"));
|
||||
await db.delete(customWidgetDefinitions).where(eq(customWidgetDefinitions.id, "seed-currency-exchange"));
|
||||
|
||||
await seedDataAsync(db);
|
||||
|
||||
const reseededDefinitions = await db.query.customWidgetDefinitions.findMany({
|
||||
where: (table, { like }) => like(table.id, "seed-%"),
|
||||
});
|
||||
const editedDefinition = reseededDefinitions.find(({ id }) => id === "seed-dog-facts");
|
||||
const reseededCurrency = reseededDefinitions.find(({ id }) => id === "seed-currency-exchange");
|
||||
|
||||
expect(reseededDefinitions.map(({ id }) => id).toSorted()).toEqual(expectedSeedIds);
|
||||
expect(editedDefinition).toMatchObject({ name: "User-edited dog facts", enabled: true, creatorId: null });
|
||||
expect(reseededDefinitions.filter(({ id }) => id !== "seed-dog-facts").every(({ enabled }) => !enabled)).toBe(true);
|
||||
if (!reseededCurrency) throw new Error("Currency Exchange seed was not restored");
|
||||
expect(reconstructWidget(reseededCurrency)).toEqual(
|
||||
BUNDLED_CUSTOM_WIDGETS.find(({ id }) => id === "seed-currency-exchange")?.widget,
|
||||
);
|
||||
expect(await db.$count(customWidgetSecrets)).toBe(0);
|
||||
};
|
||||
@@ -3,13 +3,14 @@ import { MySqlContainer } from "@testcontainers/mysql";
|
||||
import { drizzle } from "drizzle-orm/mysql2";
|
||||
import { migrate } from "drizzle-orm/mysql2/migrator";
|
||||
import mysql from "mysql2";
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { describe, test } from "vitest";
|
||||
|
||||
import { DB_CASING } from "@homarr/core/infrastructure/db/constants";
|
||||
|
||||
import * as mysqlSchema from "../schema/mysql";
|
||||
import type { Database } from "..";
|
||||
import { seedDataAsync } from "../migrations/seed";
|
||||
import { expectBundledCustomWidgetsSeeded } from "./custom-widget-seed-assertions";
|
||||
|
||||
describe("Mysql Migration", () => {
|
||||
test("should add all tables and keys specified in migration files", async () => {
|
||||
@@ -37,11 +38,7 @@ describe("Mysql Migration", () => {
|
||||
|
||||
// Check if users table exists
|
||||
await database.query.users.findMany();
|
||||
const bundledWidgets = await database.query.customWidgetDefinitions.findMany({
|
||||
where: (table, { like }) => like(table.id, "seed-%"),
|
||||
});
|
||||
expect(bundledWidgets).toHaveLength(4);
|
||||
expect(bundledWidgets.every(({ enabled }) => !enabled)).toBe(true);
|
||||
await expectBundledCustomWidgetsSeeded(database as unknown as Database);
|
||||
|
||||
connection.end();
|
||||
await mysqlContainer.stop();
|
||||
|
||||
@@ -3,13 +3,14 @@ import { PostgreSqlContainer } from "@testcontainers/postgresql";
|
||||
import { drizzle } from "drizzle-orm/node-postgres";
|
||||
import { migrate } from "drizzle-orm/node-postgres/migrator";
|
||||
import { Pool } from "pg";
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { describe, test } from "vitest";
|
||||
|
||||
import { DB_CASING } from "@homarr/core/infrastructure/db/constants";
|
||||
|
||||
import * as pgSchema from "../schema/postgresql";
|
||||
import type { Database } from "..";
|
||||
import { seedDataAsync } from "../migrations/seed";
|
||||
import { expectBundledCustomWidgetsSeeded } from "./custom-widget-seed-assertions";
|
||||
|
||||
describe("PostgreSql Migration", () => {
|
||||
test("should add all tables and keys specified in migration files", async () => {
|
||||
@@ -42,11 +43,7 @@ describe("PostgreSql Migration", () => {
|
||||
|
||||
// Check if users table exists
|
||||
await database.query.users.findMany();
|
||||
const bundledWidgets = await database.query.customWidgetDefinitions.findMany({
|
||||
where: (table, { like }) => like(table.id, "seed-%"),
|
||||
});
|
||||
expect(bundledWidgets).toHaveLength(4);
|
||||
expect(bundledWidgets.every(({ enabled }) => !enabled)).toBe(true);
|
||||
await expectBundledCustomWidgetsSeeded(database as unknown as Database);
|
||||
|
||||
// Close the pool to release resources
|
||||
await pool.end();
|
||||
|
||||
@@ -2,13 +2,14 @@ import path from "path";
|
||||
import BetterSqlite3 from "better-sqlite3";
|
||||
import { drizzle } from "drizzle-orm/better-sqlite3";
|
||||
import { migrate } from "drizzle-orm/better-sqlite3/migrator";
|
||||
import { expect, test } from "vitest";
|
||||
import { test } from "vitest";
|
||||
|
||||
import { DB_CASING } from "@homarr/core/infrastructure/db/constants";
|
||||
|
||||
import type { Database } from "..";
|
||||
import { seedDataAsync } from "../migrations/seed";
|
||||
import * as sqliteSchema from "../schema/sqlite";
|
||||
import { expectBundledCustomWidgetsSeeded } from "./custom-widget-seed-assertions";
|
||||
|
||||
test("SQLite migrations seed the four disabled bundled custom widgets", async () => {
|
||||
const connection = new BetterSqlite3(":memory:");
|
||||
@@ -17,11 +18,7 @@ test("SQLite migrations seed the four disabled bundled custom widgets", async ()
|
||||
migrate(database, { migrationsFolder: path.join(__dirname, "..", "migrations", "sqlite") });
|
||||
await seedDataAsync(database as unknown as Database);
|
||||
|
||||
const bundledWidgets = await database.query.customWidgetDefinitions.findMany({
|
||||
where: (table, { like }) => like(table.id, "seed-%"),
|
||||
});
|
||||
expect(bundledWidgets).toHaveLength(4);
|
||||
expect(bundledWidgets.every(({ enabled }) => !enabled)).toBe(true);
|
||||
await expectBundledCustomWidgetsSeeded(database as unknown as Database);
|
||||
|
||||
connection.close();
|
||||
});
|
||||
Reference in new issue
Block a user