From 8ec61a6b86783d7b3916e6b85c6512a67aaa137a Mon Sep 17 00:00:00 2001 From: Mel-906 Date: Sun, 29 Mar 2026 01:52:08 +0900 Subject: [PATCH 1/5] =?UTF-8?q?test:=20runInTransaction=E3=81=AE=E6=AD=A3?= =?UTF-8?q?=E5=B8=B8=E7=B3=BB=E3=83=BB=E3=83=AD=E3=83=BC=E3=83=AB=E3=83=90?= =?UTF-8?q?=E3=83=83=E3=82=AF=E3=83=BB=E3=83=8D=E3=82=B9=E3=83=88=E5=90=88?= =?UTF-8?q?=E6=B5=81=E3=82=92=E6=A4=9C=E8=A8=BC=E3=81=99=E3=82=8B=E3=83=86?= =?UTF-8?q?=E3=82=B9=E3=83=88=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) --- .../drizzle/runInTransaction.test.ts | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 tests/infrastructure/drizzle/runInTransaction.test.ts diff --git a/tests/infrastructure/drizzle/runInTransaction.test.ts b/tests/infrastructure/drizzle/runInTransaction.test.ts new file mode 100644 index 0000000..8f3ab3f --- /dev/null +++ b/tests/infrastructure/drizzle/runInTransaction.test.ts @@ -0,0 +1,78 @@ +import { afterAll, afterEach, beforeAll, describe, expect, it } from "vite-plus/test"; +import { eq } from "drizzle-orm"; +import { drizzle } from "drizzle-orm/node-postgres"; +import { Pool } from "pg"; +import { getClient, runInTransaction } from "#infrastructure/drizzle/client"; +import { events } from "#infrastructure/drizzle/schema"; + +const TEST_DB_URL = "postgresql://cs25009:test@localhost:5432/core_test"; + +let pool: Pool; + +beforeAll(() => { + process.env.DATABASE_URL = TEST_DB_URL; + pool = new Pool({ connectionString: TEST_DB_URL }); +}); + +afterEach(async () => { + await pool.query("DELETE FROM events WHERE id LIKE 'test-%'"); +}); + +afterAll(async () => { + await pool.end(); +}); + +function testEvent(id: string, name: string) { + const now = new Date().toISOString(); + return { id, name, date: now, updatedAt: now }; +} + +describe("runInTransaction", () => { + it("複数の書き込みがすべて永続化される", async () => { + await runInTransaction(async () => { + const client = getClient(); + await client.insert(events).values(testEvent("test-1", "Event 1")); + await client.insert(events).values(testEvent("test-2", "Event 2")); + }); + + const db = drizzle(pool); + const rows = await db.select().from(events).where(eq(events.id, "test-1")); + const rows2 = await db.select().from(events).where(eq(events.id, "test-2")); + expect(rows).toHaveLength(1); + expect(rows2).toHaveLength(1); + }); + + it("途中で例外が発生したら全て巻き戻る", async () => { + await expect( + runInTransaction(async () => { + const client = getClient(); + await client.insert(events).values(testEvent("test-rollback", "Should not persist")); + throw new Error("intentional error"); + }), + ).rejects.toThrow("intentional error"); + + const db = drizzle(pool); + const rows = await db.select().from(events).where(eq(events.id, "test-rollback")); + expect(rows).toHaveLength(0); + }); + + it("ネスト時に内側のrunInTransactionが新しいトランザクションを開始せず、外側の失敗で全体が巻き戻る", async () => { + await expect( + runInTransaction(async () => { + // 内側のrunInTransaction(Repository.save()を模倣) + await runInTransaction(async () => { + const client = getClient(); + await client.insert(events).values(testEvent("test-nested", "Nested write")); + }); + + // 内側は成功したが、外側で例外 + throw new Error("outer failure"); + }), + ).rejects.toThrow("outer failure"); + + // 内側の書き込みも巻き戻っていること + const db = drizzle(pool); + const rows = await db.select().from(events).where(eq(events.id, "test-nested")); + expect(rows).toHaveLength(0); + }); +}); From 298c65d0873420f786c2c98d1891795e70e4989b Mon Sep 17 00:00:00 2001 From: Mel-906 Date: Tue, 31 Mar 2026 01:22:30 +0900 Subject: [PATCH 2/5] =?UTF-8?q?feat:=20DB=E7=B5=90=E5=90=88=E3=83=86?= =?UTF-8?q?=E3=82=B9=E3=83=88=E5=9F=BA=E7=9B=A4=E3=81=AE=E5=B0=8E=E5=85=A5?= =?UTF-8?q?=E3=81=A8runInTransaction=E3=83=86=E3=82=B9=E3=83=88=E3=81=AE?= =?UTF-8?q?=E7=A7=BB=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docker Compose + Vitest設定分離 + テストヘルパーによる結合テスト基盤を構築し、 既存のrunInTransactionテストを新基盤に移行した。 CIにintegrationテストジョブを追加。 Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/ci.yml | 22 ++++++ docker-compose.test.yml | 16 ++++ package-lock.json | 32 ++++++++ package.json | 3 + tests/helpers/db.ts | 74 +++++++++++++++++++ tests/helpers/globalSetup.ts | 9 +++ tests/helpers/setupIntegration.ts | 9 +++ ...s => runInTransaction.integration.test.ts} | 31 ++------ vite.config.integration.ts | 20 +++++ vite.config.ts | 1 + 10 files changed, 191 insertions(+), 26 deletions(-) create mode 100644 docker-compose.test.yml create mode 100644 tests/helpers/db.ts create mode 100644 tests/helpers/globalSetup.ts create mode 100644 tests/helpers/setupIntegration.ts rename tests/infrastructure/drizzle/{runInTransaction.test.ts => runInTransaction.integration.test.ts} (71%) create mode 100644 vite.config.integration.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6b40f09..fa93646 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,6 +51,28 @@ jobs: exit 1 fi + test-integration: + runs-on: ubuntu-latest + timeout-minutes: 10 + services: + postgres: + image: postgres:17-alpine + env: + POSTGRES_USER: core_test + POSTGRES_PASSWORD: core_test + POSTGRES_DB: core_test + ports: + - 5433:5432 + options: >- + --health-cmd "pg_isready -U core_test" + --health-interval 2s + --health-timeout 5s + --health-retries 10 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: ./.github/actions/setup-node + - run: npm run test:integration + build: runs-on: ubuntu-latest timeout-minutes: 10 diff --git a/docker-compose.test.yml b/docker-compose.test.yml new file mode 100644 index 0000000..521ccb0 --- /dev/null +++ b/docker-compose.test.yml @@ -0,0 +1,16 @@ +services: + test-db: + image: postgres:17-alpine + ports: + - "5433:5432" + environment: + POSTGRES_USER: core_test + POSTGRES_PASSWORD: core_test + POSTGRES_DB: core_test + healthcheck: + test: ["CMD-SHELL", "pg_isready -U core_test"] + interval: 2s + timeout: 5s + retries: 10 + tmpfs: + - /var/lib/postgresql/data diff --git a/package-lock.json b/package-lock.json index 19aee33..9ce7353 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1977,6 +1977,22 @@ } } }, + "node_modules/@voidzero-dev/vite-plus-test/node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/@voidzero-dev/vite-plus-win32-arm64-msvc": { "version": "0.1.14", "resolved": "https://registry.npmjs.org/@voidzero-dev/vite-plus-win32-arm64-msvc/-/vite-plus-win32-arm64-msvc-0.1.14.tgz", @@ -3389,6 +3405,22 @@ } } }, + "node_modules/vite-plus/node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index c7b43f2..381378b 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,9 @@ "prepare": "npm run build", "test": "vp test", "test:watch": "vp test watch", + "test:integration": "vp test --config vite.config.integration.ts", + "test:integration:up": "docker compose -f docker-compose.test.yml up -d --wait && npm run test:integration", + "test:integration:down": "docker compose -f docker-compose.test.yml down", "typecheck": "tsc --noEmit", "check": "vp check", "lint": "vp lint", diff --git a/tests/helpers/db.ts b/tests/helpers/db.ts new file mode 100644 index 0000000..ddeadbe --- /dev/null +++ b/tests/helpers/db.ts @@ -0,0 +1,74 @@ +import { execSync } from "node:child_process"; +import { getTableName, isTable } from "drizzle-orm"; +import { drizzle } from "drizzle-orm/node-postgres"; +import { Pool } from "pg"; +import type { DrizzleClient } from "#infrastructure/drizzle/client"; +import * as schema from "#infrastructure/drizzle/schema"; + +export const TEST_DATABASE_URL = "postgresql://core_test:core_test@localhost:5433/core_test"; + +/** スキーマ定義から _prisma_migrations を除いた全テーブル名を動的に取得 */ +const EXCLUDED_TABLES = ["_prisma_migrations"]; + +function getApplicationTableNames(): string[] { + return Object.values(schema) + .filter(isTable) + .map(getTableName) + .filter((name) => !EXCLUDED_TABLES.includes(name)); +} + +let pool: Pool | null = null; +let client: DrizzleClient | null = null; + +/** テスト用プールを取得(なければ作成) */ +function getPool(): Pool { + if (!pool) { + pool = new Pool({ connectionString: TEST_DATABASE_URL }); + } + return pool; +} + +/** テスト用Drizzleクライアントを取得(アサーション用) */ +export function getTestClient(): DrizzleClient { + if (!client) { + client = drizzle(getPool(), { schema }); + } + return client; +} + +/** マイグレーションを実行してスキーマを最新にする */ +export async function setupTestDatabase(): Promise { + const p = getPool(); + + // 接続先がテスト用DBであることを確認(本番DBへの誤接続を防止) + const result = await p.query("SELECT current_database()"); + const dbName = result.rows[0].current_database; + if (dbName !== "core_test") { + throw new Error( + `テスト用DBではないDBに接続しています: "${dbName}"。接続先が core_test であることを確認してください。`, + ); + } + + // drizzle-kit pushでスキーマを同期(マイグレーションファイル不要で現在のスキーマを反映) + execSync("npx drizzle-kit push --force", { + env: { ...process.env, DATABASE_URL: TEST_DATABASE_URL }, + stdio: "pipe", + }); +} + +/** 全アプリケーションテーブルをTRUNCATEする */ +export async function cleanDatabase(): Promise { + const tableNames = getApplicationTableNames(); + if (tableNames.length === 0) return; + const p = getPool(); + await p.query(`TRUNCATE ${tableNames.join(", ")} CASCADE`); +} + +/** テスト用プールを閉じる */ +export async function teardownTestDatabase(): Promise { + if (pool) { + await pool.end(); + pool = null; + client = null; + } +} diff --git a/tests/helpers/globalSetup.ts b/tests/helpers/globalSetup.ts new file mode 100644 index 0000000..3370b5b --- /dev/null +++ b/tests/helpers/globalSetup.ts @@ -0,0 +1,9 @@ +import { setupTestDatabase, teardownTestDatabase } from "./db"; + +export async function setup() { + await setupTestDatabase(); +} + +export async function teardown() { + await teardownTestDatabase(); +} diff --git a/tests/helpers/setupIntegration.ts b/tests/helpers/setupIntegration.ts new file mode 100644 index 0000000..3a96a49 --- /dev/null +++ b/tests/helpers/setupIntegration.ts @@ -0,0 +1,9 @@ +import { beforeEach } from "vite-plus/test"; +import { TEST_DATABASE_URL, cleanDatabase } from "./db"; + +// プロダクションコードの getClient() がテスト用DBに接続するよう環境変数を設定 +process.env.DATABASE_URL = TEST_DATABASE_URL; + +beforeEach(async () => { + await cleanDatabase(); +}); diff --git a/tests/infrastructure/drizzle/runInTransaction.test.ts b/tests/infrastructure/drizzle/runInTransaction.integration.test.ts similarity index 71% rename from tests/infrastructure/drizzle/runInTransaction.test.ts rename to tests/infrastructure/drizzle/runInTransaction.integration.test.ts index 8f3ab3f..4a8d35e 100644 --- a/tests/infrastructure/drizzle/runInTransaction.test.ts +++ b/tests/infrastructure/drizzle/runInTransaction.integration.test.ts @@ -1,27 +1,9 @@ -import { afterAll, afterEach, beforeAll, describe, expect, it } from "vite-plus/test"; +import { describe, expect, it } from "vite-plus/test"; import { eq } from "drizzle-orm"; -import { drizzle } from "drizzle-orm/node-postgres"; -import { Pool } from "pg"; +import { getTestClient } from "../../helpers/db"; import { getClient, runInTransaction } from "#infrastructure/drizzle/client"; import { events } from "#infrastructure/drizzle/schema"; -const TEST_DB_URL = "postgresql://cs25009:test@localhost:5432/core_test"; - -let pool: Pool; - -beforeAll(() => { - process.env.DATABASE_URL = TEST_DB_URL; - pool = new Pool({ connectionString: TEST_DB_URL }); -}); - -afterEach(async () => { - await pool.query("DELETE FROM events WHERE id LIKE 'test-%'"); -}); - -afterAll(async () => { - await pool.end(); -}); - function testEvent(id: string, name: string) { const now = new Date().toISOString(); return { id, name, date: now, updatedAt: now }; @@ -35,7 +17,7 @@ describe("runInTransaction", () => { await client.insert(events).values(testEvent("test-2", "Event 2")); }); - const db = drizzle(pool); + const db = getTestClient(); const rows = await db.select().from(events).where(eq(events.id, "test-1")); const rows2 = await db.select().from(events).where(eq(events.id, "test-2")); expect(rows).toHaveLength(1); @@ -51,7 +33,7 @@ describe("runInTransaction", () => { }), ).rejects.toThrow("intentional error"); - const db = drizzle(pool); + const db = getTestClient(); const rows = await db.select().from(events).where(eq(events.id, "test-rollback")); expect(rows).toHaveLength(0); }); @@ -59,19 +41,16 @@ describe("runInTransaction", () => { it("ネスト時に内側のrunInTransactionが新しいトランザクションを開始せず、外側の失敗で全体が巻き戻る", async () => { await expect( runInTransaction(async () => { - // 内側のrunInTransaction(Repository.save()を模倣) await runInTransaction(async () => { const client = getClient(); await client.insert(events).values(testEvent("test-nested", "Nested write")); }); - // 内側は成功したが、外側で例外 throw new Error("outer failure"); }), ).rejects.toThrow("outer failure"); - // 内側の書き込みも巻き戻っていること - const db = drizzle(pool); + const db = getTestClient(); const rows = await db.select().from(events).where(eq(events.id, "test-nested")); expect(rows).toHaveLength(0); }); diff --git a/vite.config.integration.ts b/vite.config.integration.ts new file mode 100644 index 0000000..37bc993 --- /dev/null +++ b/vite.config.integration.ts @@ -0,0 +1,20 @@ +import { fileURLToPath } from "node:url"; +import { defineConfig } from "vite-plus"; + +export default defineConfig({ + resolve: { + alias: { + "#domain": fileURLToPath(new URL("./src/domain", import.meta.url)), + "#application": fileURLToPath(new URL("./src/application", import.meta.url)), + "#infrastructure": fileURLToPath(new URL("./src/infrastructure", import.meta.url)), + }, + }, + test: { + include: ["tests/**/*.integration.test.ts"], + globalSetup: ["tests/helpers/globalSetup.ts"], + setupFiles: ["tests/helpers/setupIntegration.ts"], + testTimeout: 30_000, + hookTimeout: 30_000, + fileParallelism: false, + }, +}); diff --git a/vite.config.ts b/vite.config.ts index ee3df83..9e4f5cb 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -11,6 +11,7 @@ export default defineConfig({ }, test: { include: ["tests/**/*.test.ts"], + exclude: ["tests/**/*.integration.test.ts"], }, lint: { ignorePatterns: ["dist/**", "drizzle/**", "example/**"], From 7bd75f16d777891d665a209ae97ff9fb099e7e22 Mon Sep 17 00:00:00 2001 From: Mel-906 Date: Thu, 2 Apr 2026 21:02:16 +0900 Subject: [PATCH 3/5] test: harden integration test setup --- .github/workflows/ci.yml | 26 ++++++++-------- docker-compose.test.yml | 10 +++---- package.json | 4 +-- tests/helpers/config.ts | 49 +++++++++++++++++++++++++++++++ tests/helpers/db.ts | 31 +++++++++++-------- tests/helpers/setupIntegration.ts | 6 ++-- 6 files changed, 88 insertions(+), 38 deletions(-) create mode 100644 tests/helpers/config.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fa93646..ee7ba92 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,24 +54,22 @@ jobs: test-integration: runs-on: ubuntu-latest timeout-minutes: 10 - services: - postgres: - image: postgres:17-alpine - env: - POSTGRES_USER: core_test - POSTGRES_PASSWORD: core_test - POSTGRES_DB: core_test - ports: - - 5433:5432 - options: >- - --health-cmd "pg_isready -U core_test" - --health-interval 2s - --health-timeout 5s - --health-retries 10 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: ./.github/actions/setup-node + - name: Create test env file + run: | + cat <<'EOF' > .env.test + POSTGRES_HOST=localhost + POSTGRES_USER=core_test + POSTGRES_PASSWORD=core_test + POSTGRES_DB=core_test + POSTGRES_PORT=5433 + EOF + - run: docker compose --env-file .env.test -f docker-compose.test.yml up -d --wait - run: npm run test:integration + - if: always() + run: docker compose --env-file .env.test -f docker-compose.test.yml down build: runs-on: ubuntu-latest diff --git a/docker-compose.test.yml b/docker-compose.test.yml index 521ccb0..31b0ac0 100644 --- a/docker-compose.test.yml +++ b/docker-compose.test.yml @@ -2,13 +2,13 @@ services: test-db: image: postgres:17-alpine ports: - - "5433:5432" + - "${POSTGRES_PORT}:5432" environment: - POSTGRES_USER: core_test - POSTGRES_PASSWORD: core_test - POSTGRES_DB: core_test + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} healthcheck: - test: ["CMD-SHELL", "pg_isready -U core_test"] + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] interval: 2s timeout: 5s retries: 10 diff --git a/package.json b/package.json index 381378b..22c8a54 100644 --- a/package.json +++ b/package.json @@ -33,8 +33,8 @@ "test": "vp test", "test:watch": "vp test watch", "test:integration": "vp test --config vite.config.integration.ts", - "test:integration:up": "docker compose -f docker-compose.test.yml up -d --wait && npm run test:integration", - "test:integration:down": "docker compose -f docker-compose.test.yml down", + "test:integration:up": "docker compose --env-file .env.test -f docker-compose.test.yml up -d --wait && npm run test:integration", + "test:integration:down": "docker compose --env-file .env.test -f docker-compose.test.yml down", "typecheck": "tsc --noEmit", "check": "vp check", "lint": "vp lint", diff --git a/tests/helpers/config.ts b/tests/helpers/config.ts new file mode 100644 index 0000000..73672f3 --- /dev/null +++ b/tests/helpers/config.ts @@ -0,0 +1,49 @@ +import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import { parseEnv } from "node:util"; + +const TEST_ENV_PATH = fileURLToPath(new URL("../../.env.test", import.meta.url)); + +function loadTestEnv(): Record { + return parseEnv(readFileSync(TEST_ENV_PATH, "utf8")); +} + +function requireEnv(env: Record, name: string): string { + const value = env[name]; + if (!value) { + throw new Error(`${name} is not set in .env.test`); + } + return value; +} + +const loadedEnv = loadTestEnv(); + +for (const [key, value] of Object.entries(loadedEnv)) { + process.env[key] = value; +} + +export const testConfig = { + db: { + host: requireEnv(loadedEnv, "POSTGRES_HOST"), + port: Number(requireEnv(loadedEnv, "POSTGRES_PORT")), + name: requireEnv(loadedEnv, "POSTGRES_DB"), + user: requireEnv(loadedEnv, "POSTGRES_USER"), + password: requireEnv(loadedEnv, "POSTGRES_PASSWORD"), + }, +} as const; + +const databaseUrl = new URL( + `postgresql://${testConfig.db.host}:${testConfig.db.port}/${testConfig.db.name}`, +); +databaseUrl.username = testConfig.db.user; +databaseUrl.password = testConfig.db.password; + +export const TEST_DATABASE_URL = databaseUrl.toString(); + +process.env.DATABASE_URL = TEST_DATABASE_URL; + +export const testProcessEnv: NodeJS.ProcessEnv = { + ...process.env, + ...loadedEnv, + DATABASE_URL: TEST_DATABASE_URL, +}; diff --git a/tests/helpers/db.ts b/tests/helpers/db.ts index ddeadbe..9c12145 100644 --- a/tests/helpers/db.ts +++ b/tests/helpers/db.ts @@ -1,20 +1,24 @@ import { execSync } from "node:child_process"; import { getTableName, isTable } from "drizzle-orm"; import { drizzle } from "drizzle-orm/node-postgres"; -import { Pool } from "pg"; +import { escapeIdentifier, Pool } from "pg"; import type { DrizzleClient } from "#infrastructure/drizzle/client"; import * as schema from "#infrastructure/drizzle/schema"; - -export const TEST_DATABASE_URL = "postgresql://core_test:core_test@localhost:5433/core_test"; +import { TEST_DATABASE_URL, testConfig, testProcessEnv } from "./config"; /** スキーマ定義から _prisma_migrations を除いた全テーブル名を動的に取得 */ -const EXCLUDED_TABLES = ["_prisma_migrations"]; +const EXCLUDED_TABLES = new Set(["_prisma_migrations"]); +// TODO: #143 が解決されたら _prisma_migrations の暫定除外を削除する。 function getApplicationTableNames(): string[] { - return Object.values(schema) - .filter(isTable) - .map(getTableName) - .filter((name) => !EXCLUDED_TABLES.includes(name)); + const tableNames: string[] = []; + + for (const value of Object.values(schema) as unknown[]) { + if (!isTable(value)) continue; + tableNames.push(getTableName(value)); + } + + return tableNames.filter((name) => !EXCLUDED_TABLES.has(name)); } let pool: Pool | null = null; @@ -43,15 +47,15 @@ export async function setupTestDatabase(): Promise { // 接続先がテスト用DBであることを確認(本番DBへの誤接続を防止) const result = await p.query("SELECT current_database()"); const dbName = result.rows[0].current_database; - if (dbName !== "core_test") { + if (dbName !== testConfig.db.name) { throw new Error( - `テスト用DBではないDBに接続しています: "${dbName}"。接続先が core_test であることを確認してください。`, + `テスト用DBではないDBに接続しています: "${dbName}"。接続先が ${testConfig.db.name} であることを確認してください。`, ); } // drizzle-kit pushでスキーマを同期(マイグレーションファイル不要で現在のスキーマを反映) - execSync("npx drizzle-kit push --force", { - env: { ...process.env, DATABASE_URL: TEST_DATABASE_URL }, + execSync("vp run db:push -- --force", { + env: testProcessEnv, stdio: "pipe", }); } @@ -61,7 +65,8 @@ export async function cleanDatabase(): Promise { const tableNames = getApplicationTableNames(); if (tableNames.length === 0) return; const p = getPool(); - await p.query(`TRUNCATE ${tableNames.join(", ")} CASCADE`); + const quotedTableNames = tableNames.map(escapeIdentifier); + await p.query(`TRUNCATE ${quotedTableNames.join(", ")} CASCADE`); } /** テスト用プールを閉じる */ diff --git a/tests/helpers/setupIntegration.ts b/tests/helpers/setupIntegration.ts index 3a96a49..8461c31 100644 --- a/tests/helpers/setupIntegration.ts +++ b/tests/helpers/setupIntegration.ts @@ -1,8 +1,6 @@ import { beforeEach } from "vite-plus/test"; -import { TEST_DATABASE_URL, cleanDatabase } from "./db"; - -// プロダクションコードの getClient() がテスト用DBに接続するよう環境変数を設定 -process.env.DATABASE_URL = TEST_DATABASE_URL; +import "./config"; +import { cleanDatabase } from "./db"; beforeEach(async () => { await cleanDatabase(); From 75545e6919a994c86ff0fd9e588c34d2d7f16d13 Mon Sep 17 00:00:00 2001 From: Mel-906 Date: Thu, 2 Apr 2026 22:51:13 +0900 Subject: [PATCH 4/5] test: use postgres 15 for integration tests --- .github/workflows/ci.yml | 1 + docker-compose.test.yml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ee7ba92..b99a54f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,6 +61,7 @@ jobs: run: | cat <<'EOF' > .env.test POSTGRES_HOST=localhost + POSTGRES_VERSION=15 POSTGRES_USER=core_test POSTGRES_PASSWORD=core_test POSTGRES_DB=core_test diff --git a/docker-compose.test.yml b/docker-compose.test.yml index 31b0ac0..0b24f1c 100644 --- a/docker-compose.test.yml +++ b/docker-compose.test.yml @@ -1,6 +1,6 @@ services: test-db: - image: postgres:17-alpine + image: "postgres:${POSTGRES_VERSION:-15}" ports: - "${POSTGRES_PORT}:5432" environment: From 1ca926fef49a114c57ab74a78eba7d312d7a7cfb Mon Sep 17 00:00:00 2001 From: Mel-906 Date: Sat, 25 Apr 2026 01:56:55 +0900 Subject: [PATCH 5/5] fix:version of postgreSQL 15 --- docker-compose.test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.test.yml b/docker-compose.test.yml index 0b24f1c..0a7aa6a 100644 --- a/docker-compose.test.yml +++ b/docker-compose.test.yml @@ -1,6 +1,6 @@ services: test-db: - image: "postgres:${POSTGRES_VERSION:-15}" + image: "postgres:${POSTGRES_VERSION:-15}" #15のバージョン ports: - "${POSTGRES_PORT}:5432" environment: