From ec899079e98b628150acd3f4ef2caa3c4ff96f25 Mon Sep 17 00:00:00 2001 From: Carl Henderson Date: Wed, 17 Apr 2024 14:10:34 +0100 Subject: [PATCH] Auto run d1 migrations --- src/test.ts | 73 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 54 insertions(+), 19 deletions(-) diff --git a/src/test.ts b/src/test.ts index 530c1b4..3eae275 100644 --- a/src/test.ts +++ b/src/test.ts @@ -1,11 +1,13 @@ -import { Miniflare, createFetchMock } from "miniflare"; -import toml from "toml"; import fs from "fs"; -import { Env } from "./types"; +import { JWTPayload, SignJWT, exportJWK, generateKeyPair } from "jose"; +import { Miniflare, createFetchMock } from "miniflare"; +import nodefs from "node:fs/promises"; +import path from "node:path"; +import createClient from "openapi-fetch"; import consumers from "stream/consumers"; +import toml from "toml"; import { URLSearchParams } from "url"; -import createClient from "openapi-fetch"; -import { generateKeyPair, SignJWT, JWTPayload, exportJWK } from "jose"; +import { Env } from "./types"; // eslint-disable-next-line @typescript-eslint/ban-types export const setupTests = async (bindings: Env) => { @@ -23,38 +25,71 @@ export const setupTests = async (bindings: Env) => { ...bindings, }, queueConsumers: (config.queues?.consumers ?? []).map( - ({ queue }: { queue: string }) => queue, + ({ queue }: { queue: string }) => queue ), queueProducers: Object.fromEntries( (config.queues?.producers ?? []).map( ({ binding, queue }: { queue: string; binding: string }) => [ binding, queue, - ], - ), + ] + ) ), r2Buckets: (config.r2_buckets ?? []).map( - ({ binding }: { binding: string }) => binding, + ({ binding }: { binding: string }) => binding ), kvNamespaces: (config.kv_namespaces ?? []).map( - ({ binding }: { binding: string }) => binding, + ({ binding }: { binding: string }) => binding ), d1Databases: (config.d1_databases ?? []).map( - ({ binding }: { binding: string }) => binding, + ({ binding }: { binding: string }) => binding ), serviceBindings: Object.fromEntries( (config.services ?? []).map( ({ binding, service }: { binding: string; service: string }) => [ binding, service, - ], - ), + ] + ) ), fetchMock, }); const url = await miniflare.ready; + // Hack until miniflare supports auto running D1 migrations. + // Note: Each migration file can contain only a single statement. + await Promise.all( + (config.d1_databases ?? []).map( + async ({ binding }: { binding: string }) => { + const d1 = await miniflare.getD1Database(binding); + + const migrationsDir = "./migrations"; + for (const migrationName of (await nodefs.readdir(migrationsDir)).sort( + (a, b) => { + const migrationNumberA = parseInt(a.split("_")[0]!); + const migrationNumberB = parseInt(b.split("_")[0]!); + if (migrationNumberA < migrationNumberB) { + return -1; + } + if (migrationNumberA > migrationNumberB) { + return 1; + } + + // numbers must be equal + return 0; + } + )) { + const migrationPath = path.join(migrationsDir, migrationName); + let migration = await nodefs.readFile(migrationPath, "utf8"); + // Remove migration comment + migration = migration.split("\n").slice(1).join(" "); + await d1.exec(migration); + } + } + ) + ); + const client = createClient({ baseUrl: url.toString() }); return { @@ -91,7 +126,7 @@ export const setupTests = async (bindings: Env) => { headers: { "Content-Type": "application/json", }, - }, + } ) .persist(); fetchMock @@ -106,7 +141,7 @@ export const setupTests = async (bindings: Env) => { headers: { "Content-Type": "application/json", }, - }, + } ) .persist(); return await new SignJWT(claims) @@ -138,7 +173,7 @@ export function recordRequest( data: string | object | Buffer | undefined, responseOptions?: { headers: Record; - }, + } ) { // eslint-disable-next-line @typescript-eslint/no-explicit-any return ({ body }: any) => { @@ -154,7 +189,7 @@ export function recordFormRequest( data: string | object | Buffer | undefined, responseOptions?: { headers: Record; - }, + } ) { // eslint-disable-next-line @typescript-eslint/no-explicit-any return ({ body }: any) => { @@ -162,7 +197,7 @@ export function recordFormRequest( .text(body) // eslint-disable-next-line @typescript-eslint/no-explicit-any .then((data: any) => - cb(Object.fromEntries(new URLSearchParams(data).entries())), + cb(Object.fromEntries(new URLSearchParams(data).entries())) ); return { statusCode, data, responseOptions }; }; @@ -175,7 +210,7 @@ export function recordFirehoseRequest( data: string | object | Buffer | undefined, responseOptions?: { headers: Record; - }, + } ) { // eslint-disable-next-line @typescript-eslint/no-explicit-any return ({ body }: any) => {