Skip to content

Commit 43cd15f

Browse files
committed
New structure working
1 parent 80048fc commit 43cd15f

File tree

5 files changed

+118
-80
lines changed

5 files changed

+118
-80
lines changed

src/index.ts

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type {
1212
GetTestPostgresDatabase,
1313
GetTestPostgresDatabaseFactoryOptions,
1414
GetTestPostgresDatabaseOptions,
15+
GetTestPostgresDatabaseResult,
1516
} from "./public-types"
1617
import { Pool } from "pg"
1718
import type { Jsonifiable } from "type-fest"
@@ -104,7 +105,7 @@ export const getTestPostgresDatabaseFactory = <
104105
Params extends Jsonifiable = never
105106
>(
106107
options?: GetTestPostgresDatabaseFactoryOptions<Params>
107-
) => {
108+
): GetTestPostgresDatabase<Params> => {
108109
const initialData: InitialWorkerData = {
109110
postgresVersion: options?.postgresVersion ?? "14",
110111
containerOptions: options?.container,
@@ -157,41 +158,28 @@ export const getTestPostgresDatabaseFactory = <
157158
throw error
158159
})
159160

160-
const createdNestedConnections: ConnectionDetails[] = []
161161
const hookResult = await options.beforeTemplateIsBaked({
162162
params: params as any,
163163
connection: connectionDetails,
164164
containerExec: async (command): Promise<ExecResult> =>
165165
rpc.execCommandInContainer(command),
166166
// This is what allows a consumer to get a "nested" database from within their beforeTemplateIsBaked hook
167-
beforeTemplateIsBaked: async (options) => {
168-
const { connectionDetails, beforeTemplateIsBakedResult } =
169-
await rpc.getTestDatabase({
170-
params: options.params,
171-
databaseDedupeKey: options.databaseDedupeKey,
172-
})
173-
174-
const mappedConnection =
167+
manuallyBuildAdditionalTemplate: async () => {
168+
const connection =
175169
mapWorkerConnectionDetailsToConnectionDetails(
176-
connectionDetails
170+
await rpc.createEmptyDatabase()
177171
)
178172

179-
createdNestedConnections.push(mappedConnection)
180-
181173
return {
182-
...mappedConnection,
183-
beforeTemplateIsBakedResult,
174+
connection,
175+
finish: async () => {
176+
await teardownConnection(connection)
177+
return rpc.convertDatabaseToTemplate(connection.database)
178+
},
184179
}
185180
},
186181
})
187182

188-
await Promise.all(
189-
createdNestedConnections.map(async (connection) => {
190-
await teardownConnection(connection)
191-
await rpc.dropDatabase(connection.database)
192-
})
193-
)
194-
195183
await teardownConnection(connectionDetails)
196184

197185
if (hookResult && !isSerializable(hookResult)) {
@@ -226,11 +214,11 @@ export const getTestPostgresDatabaseFactory = <
226214
}
227215
})()
228216

229-
const getTestPostgresDatabase: GetTestPostgresDatabase<Params> = async (
217+
const getTestPostgresDatabase = async (
230218
t: ExecutionContext,
231219
params: any,
232220
getTestDatabaseOptions?: GetTestPostgresDatabaseOptions
233-
) => {
221+
): Promise<GetTestPostgresDatabaseResult> => {
234222
const testDatabaseConnection = await rpc.getTestDatabase({
235223
databaseDedupeKey: getTestDatabaseOptions?.databaseDedupeKey,
236224
params,
@@ -251,7 +239,22 @@ export const getTestPostgresDatabaseFactory = <
251239
}
252240
}
253241

254-
return getTestPostgresDatabase
242+
getTestPostgresDatabase.fromTemplate = async (
243+
t: ExecutionContext,
244+
templateName: string
245+
) => {
246+
const connection = mapWorkerConnectionDetailsToConnectionDetails(
247+
await rpc.createDatabaseFromTemplate(templateName)
248+
)
249+
250+
t.teardown(async () => {
251+
await teardownConnection(connection)
252+
})
253+
254+
return connection
255+
}
256+
257+
return getTestPostgresDatabase as any
255258
}
256259

257260
export * from "./public-types"

src/internal-types.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,11 @@ export interface SharedWorkerFunctions {
3030
beforeTemplateIsBakedResult: unknown
3131
}>
3232
execCommandInContainer: (command: string[]) => Promise<ExecResult>
33-
dropDatabase: (databaseName: string) => Promise<void>
33+
createEmptyDatabase: () => Promise<ConnectionDetailsFromWorker>
34+
createDatabaseFromTemplate: (
35+
templateName: string
36+
) => Promise<ConnectionDetailsFromWorker>
37+
convertDatabaseToTemplate: (
38+
databaseName: string
39+
) => Promise<{ templateName: string }>
3440
}

src/public-types.ts

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import type { Pool } from "pg"
22
import type { Jsonifiable } from "type-fest"
3-
import { ExecutionContext } from "ava"
4-
import { ExecResult } from "testcontainers"
5-
import { BindMode } from "testcontainers/build/types"
3+
import type { ExecutionContext } from "ava"
4+
import type { ExecResult } from "testcontainers"
5+
import type { BindMode } from "testcontainers/build/types"
66

77
export interface ConnectionDetails {
88
connectionString: string
@@ -92,11 +92,10 @@ export interface GetTestPostgresDatabaseFactoryOptions<
9292
* })
9393
* ```
9494
*/
95-
beforeTemplateIsBaked: (
96-
options: {
97-
params: Params
98-
} & Pick<GetTestPostgresDatabaseOptions, "databaseDedupeKey">
99-
) => Promise<GetTestPostgresDatabaseResult>
95+
manuallyBuildAdditionalTemplate: () => Promise<{
96+
connection: ConnectionDetails
97+
finish: () => Promise<{ templateName: string }>
98+
}>
10099
}) => Promise<any>
101100
}
102101

@@ -155,14 +154,23 @@ export type GetTestPostgresDatabaseOptions = {
155154
// https://github.com/microsoft/TypeScript/issues/23182#issuecomment-379091887
156155
type IsNeverType<T> = [T] extends [never] ? true : false
157156

157+
interface BaseGetTestPostgresDatabase {
158+
fromTemplate(
159+
t: ExecutionContext,
160+
templateName: string
161+
): Promise<ConnectionDetails>
162+
}
163+
158164
export type GetTestPostgresDatabase<Params> = IsNeverType<Params> extends true
159-
? (
165+
? ((
160166
t: ExecutionContext,
161167
args?: null,
162168
options?: GetTestPostgresDatabaseOptions
163-
) => Promise<GetTestPostgresDatabaseResult>
164-
: (
169+
) => Promise<GetTestPostgresDatabaseResult>) &
170+
BaseGetTestPostgresDatabase
171+
: ((
165172
t: ExecutionContext,
166173
args: Params,
167174
options?: GetTestPostgresDatabaseOptions
168-
) => Promise<GetTestPostgresDatabaseResult>
175+
) => Promise<GetTestPostgresDatabaseResult>) &
176+
BaseGetTestPostgresDatabase

src/tests/hooks.test.ts

Lines changed: 22 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -148,46 +148,39 @@ test("beforeTemplateIsBaked (result isn't serializable)", async (t) => {
148148
})
149149

150150
test("beforeTemplateIsBaked, get nested database", async (t) => {
151-
type DatabaseParams = {
152-
type: "foo" | "bar"
153-
}
154-
155-
let nestedDatabaseName: string | undefined = undefined
156-
157-
const getTestServer = getTestPostgresDatabaseFactory<DatabaseParams>({
151+
const getTestDatabase = getTestPostgresDatabaseFactory({
158152
postgresVersion: process.env.POSTGRES_VERSION,
159153
workerDedupeKey: "beforeTemplateIsBakedHookNestedDatabase",
160154
beforeTemplateIsBaked: async ({
161-
params,
162155
connection: { pool },
163-
beforeTemplateIsBaked,
156+
manuallyBuildAdditionalTemplate,
164157
}) => {
165-
if (params.type === "foo") {
166-
await pool.query(`CREATE TABLE "foo" ("id" SERIAL PRIMARY KEY)`)
167-
return { createdFoo: true }
168-
}
169-
170158
await pool.query(`CREATE TABLE "bar" ("id" SERIAL PRIMARY KEY)`)
171-
const fooDatabase = await beforeTemplateIsBaked({
172-
params: { type: "foo" },
173-
})
174-
t.deepEqual(fooDatabase.beforeTemplateIsBakedResult, { createdFoo: true })
175159

176-
nestedDatabaseName = fooDatabase.database
160+
const fooTemplateBuilder = await manuallyBuildAdditionalTemplate()
161+
await fooTemplateBuilder.connection.pool.query(
162+
`CREATE TABLE "foo" ("id" SERIAL PRIMARY KEY)`
163+
)
164+
const { templateName: fooTemplateName } =
165+
await fooTemplateBuilder.finish()
177166

178-
await t.notThrowsAsync(async () => {
179-
await fooDatabase.pool.query(`INSERT INTO "foo" DEFAULT VALUES`)
180-
})
181-
182-
return { createdBar: true }
167+
return { fooTemplateName }
183168
},
184169
})
185170

186-
const database = await getTestServer(t, { type: "bar" })
187-
t.deepEqual(database.beforeTemplateIsBakedResult, { createdBar: true })
171+
const barDatabase = await getTestDatabase(t)
172+
t.truthy(barDatabase.beforeTemplateIsBakedResult.fooTemplateName)
188173

189-
t.false(
190-
await doesDatabaseExist(database.pool, nestedDatabaseName!),
191-
"Nested database should have been cleaned up after the parent hook completed"
174+
const fooDatabase = await getTestDatabase.fromTemplate(
175+
t,
176+
barDatabase.beforeTemplateIsBakedResult.fooTemplateName
192177
)
178+
179+
await t.notThrowsAsync(async () => {
180+
await fooDatabase.pool.query('SELECT * FROM "foo"')
181+
})
182+
183+
await t.throwsAsync(async () => {
184+
await fooDatabase.pool.query('SELECT * FROM "bar"')
185+
})
193186
})

src/worker.ts

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,31 @@ export class Worker {
7373
const container = (await this.startContainerPromise).container
7474
return container.exec(command)
7575
},
76-
dropDatabase: async (databaseName) => {
76+
createEmptyDatabase: async () => {
7777
const { postgresClient } = await this.startContainerPromise
78-
await postgresClient.query(`DROP DATABASE ${databaseName}`)
78+
const databaseName = getRandomDatabaseName()
79+
await postgresClient.query(`CREATE DATABASE ${databaseName}`)
80+
return this.getConnectionDetails(databaseName)
81+
},
82+
createDatabaseFromTemplate: async (templateName) => {
83+
const { postgresClient } = await this.startContainerPromise
84+
const databaseName = getRandomDatabaseName()
85+
await postgresClient.query(
86+
`CREATE DATABASE ${databaseName} WITH TEMPLATE ${templateName};`
87+
)
88+
89+
testWorker.teardown(async () => {
90+
await this.teardownDatabase(databaseName)
91+
})
92+
93+
return this.getConnectionDetails(databaseName)
94+
},
95+
convertDatabaseToTemplate: async (databaseName) => {
96+
const { postgresClient } = await this.startContainerPromise
97+
await postgresClient.query(
98+
`ALTER DATABASE ${databaseName} WITH is_template TRUE;`
99+
)
100+
return { templateName: databaseName }
79101
},
80102
},
81103
rpcChannel
@@ -152,17 +174,7 @@ export class Worker {
152174
return
153175
}
154176

155-
try {
156-
await this.forceDisconnectClientsFrom(databaseName!)
157-
await postgresClient.query(`DROP DATABASE ${databaseName}`)
158-
} catch (error) {
159-
if ((error as Error)?.message?.includes("does not exist")) {
160-
// Database was likely a nested database and manually dropped by the test worker, ignore
161-
return
162-
}
163-
164-
throw error
165-
}
177+
await this.teardownDatabase(databaseName!)
166178
})
167179

168180
return {
@@ -171,6 +183,22 @@ export class Worker {
171183
}
172184
}
173185

186+
private async teardownDatabase(databaseName: string) {
187+
const { postgresClient } = await this.startContainerPromise
188+
189+
try {
190+
await this.forceDisconnectClientsFrom(databaseName!)
191+
await postgresClient.query(`DROP DATABASE ${databaseName}`)
192+
} catch (error) {
193+
if ((error as Error)?.message?.includes("does not exist")) {
194+
// Database was likely a nested database and manually dropped by the test worker, ignore
195+
return
196+
}
197+
198+
throw error
199+
}
200+
}
201+
174202
private async createTemplate(rpc: WorkerRpc, params?: Jsonifiable) {
175203
const databaseName = getRandomDatabaseName()
176204

0 commit comments

Comments
 (0)