@dismissible/nestjs-postgres-storage 0.0.2-canary.8976e84.0 → 0.0.2-canary.b0d8bfe.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,12 @@
1
+ -- CreateTable
2
+ CREATE TABLE "dismissible_items" (
3
+ "id" TEXT NOT NULL,
4
+ "user_id" TEXT NOT NULL,
5
+ "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
6
+ "dismissed_at" TIMESTAMP(3),
7
+
8
+ CONSTRAINT "dismissible_items_pkey" PRIMARY KEY ("user_id","id")
9
+ );
10
+
11
+ -- CreateIndex
12
+ CREATE INDEX "dismissible_items_user_id_idx" ON "dismissible_items"("user_id");
@@ -0,0 +1,3 @@
1
+ # Please do not edit this file manually
2
+ # It should be added in your version-control system (e.g., Git)
3
+ provider = "postgresql"
@@ -15,7 +15,6 @@ model DismissibleItem {
15
15
  userId String @map("user_id")
16
16
  createdAt DateTime @default(now()) @map("created_at")
17
17
  dismissedAt DateTime? @map("dismissed_at")
18
- metadata Json?
19
18
 
20
19
  @@id([userId, id])
21
20
  @@index([userId])
@@ -0,0 +1,33 @@
1
+ import { fileURLToPath } from 'url';
2
+ import { dirname, join } from 'path';
3
+ import { defineConfig } from 'prisma/config';
4
+
5
+ const __dirname = dirname(fileURLToPath(import.meta.url));
6
+ const prismaDir = join(__dirname, 'prisma');
7
+
8
+ /**
9
+ * Prisma configuration for @dismissible/nestjs-postgres-storage.
10
+ *
11
+ * This config is used by the dismissible-prisma CLI and can be used directly
12
+ * by consumers who need to extend or customize the configuration.
13
+ *
14
+ * For consumers who want to use the base config in their own prisma.config.mjs:
15
+ *
16
+ * @example
17
+ * ```javascript
18
+ * import { defineConfig } from 'prisma/config';
19
+ * import { basePrismaConfig } from '@dismissible/nestjs-postgres-storage';
20
+ *
21
+ * export default defineConfig(basePrismaConfig);
22
+ * ```
23
+ */
24
+ export default defineConfig({
25
+ schema: join(prismaDir, 'schema.prisma'),
26
+ migrations: {
27
+ path: join(prismaDir, 'migrations'),
28
+ },
29
+ datasource: {
30
+ url:
31
+ process.env.DATABASE_URL ?? process.env.DISMISSIBLE_POSTGRES_STORAGE_CONNECTION_STRING ?? '',
32
+ },
33
+ });
package/src/index.ts CHANGED
@@ -2,4 +2,5 @@ export * from './postgres-storage.adapter';
2
2
  export * from './postgres-storage.config';
3
3
  export * from './postgres-storage.module';
4
4
  export * from './prisma.service';
5
+ export * from './prisma-config';
5
6
  export * from './schema-path';
@@ -1,5 +1,4 @@
1
1
  import { mock } from 'ts-jest-mocker';
2
- import { Prisma } from '../prisma/generated/prisma/client';
3
2
  import { PostgresStorageAdapter } from './postgres-storage.adapter';
4
3
  import { PrismaService } from './prisma.service';
5
4
  import { IDismissibleLogger } from '@dismissible/nestjs-logger';
@@ -19,17 +18,14 @@ describe('PostgresStorageAdapter', () => {
19
18
  beforeEach(() => {
20
19
  mockLogger = mock<IDismissibleLogger>({ failIfMockNotProvided: false });
21
20
 
22
- // Use real DismissibleItemFactory since it's a simple factory
23
21
  mockItemFactory = new DismissibleItemFactory();
24
22
 
25
- // Create a mock for the dismissibleItem delegate
26
23
  mockDismissibleItem = {
27
24
  findUnique: jest.fn(),
28
25
  create: jest.fn(),
29
26
  update: jest.fn(),
30
27
  };
31
28
 
32
- // Create a partial mock of PrismaService with the dismissibleItem property
33
29
  mockPrismaService = {
34
30
  dismissibleItem: mockDismissibleItem,
35
31
  } as unknown as PrismaService;
@@ -64,7 +60,6 @@ describe('PostgresStorageAdapter', () => {
64
60
  userId: 'user-123',
65
61
  createdAt: new Date('2024-01-15T10:30:00.000Z'),
66
62
  dismissedAt: null,
67
- metadata: null,
68
63
  };
69
64
  mockDismissibleItem.findUnique.mockResolvedValue(dbItem);
70
65
 
@@ -75,34 +70,12 @@ describe('PostgresStorageAdapter', () => {
75
70
  userId: 'user-123',
76
71
  createdAt: new Date('2024-01-15T10:30:00.000Z'),
77
72
  dismissedAt: undefined,
78
- metadata: undefined,
79
73
  });
80
74
  expect(mockLogger.debug).toHaveBeenCalledWith('PostgreSQL storage hit', {
81
75
  userId: 'user-123',
82
76
  itemId: 'item-456',
83
77
  });
84
78
  });
85
-
86
- it('should return item with metadata when present', async () => {
87
- const dbItem = {
88
- id: 'item-456',
89
- userId: 'user-123',
90
- createdAt: new Date('2024-01-15T10:30:00.000Z'),
91
- dismissedAt: new Date('2024-01-15T12:00:00.000Z'),
92
- metadata: { version: 2, category: 'promotional' },
93
- };
94
- mockDismissibleItem.findUnique.mockResolvedValue(dbItem);
95
-
96
- const result = await adapter.get('user-123', 'item-456');
97
-
98
- expect(result).toEqual({
99
- id: 'item-456',
100
- userId: 'user-123',
101
- createdAt: new Date('2024-01-15T10:30:00.000Z'),
102
- dismissedAt: new Date('2024-01-15T12:00:00.000Z'),
103
- metadata: { version: 2, category: 'promotional' },
104
- });
105
- });
106
79
  });
107
80
 
108
81
  describe('create', () => {
@@ -117,7 +90,6 @@ describe('PostgresStorageAdapter', () => {
117
90
  userId: 'user-123',
118
91
  createdAt: new Date('2024-01-15T10:30:00.000Z'),
119
92
  dismissedAt: null,
120
- metadata: null,
121
93
  };
122
94
  mockDismissibleItem.create.mockResolvedValue(dbItem);
123
95
 
@@ -128,7 +100,6 @@ describe('PostgresStorageAdapter', () => {
128
100
  userId: 'user-123',
129
101
  createdAt: new Date('2024-01-15T10:30:00.000Z'),
130
102
  dismissedAt: undefined,
131
- metadata: undefined,
132
103
  });
133
104
  expect(mockDismissibleItem.create).toHaveBeenCalledWith({
134
105
  data: {
@@ -136,7 +107,6 @@ describe('PostgresStorageAdapter', () => {
136
107
  userId: 'user-123',
137
108
  createdAt: new Date('2024-01-15T10:30:00.000Z'),
138
109
  dismissedAt: null,
139
- metadata: Prisma.JsonNull,
140
110
  },
141
111
  });
142
112
  expect(mockLogger.debug).toHaveBeenCalledWith('PostgreSQL storage create', {
@@ -144,33 +114,6 @@ describe('PostgresStorageAdapter', () => {
144
114
  itemId: 'item-456',
145
115
  });
146
116
  });
147
-
148
- it('should create an item with metadata', async () => {
149
- const item: DismissibleItemDto = {
150
- id: 'item-456',
151
- userId: 'user-123',
152
- createdAt: new Date('2024-01-15T10:30:00.000Z'),
153
- metadata: { version: 2 },
154
- };
155
- const dbItem = {
156
- id: 'item-456',
157
- userId: 'user-123',
158
- createdAt: new Date('2024-01-15T10:30:00.000Z'),
159
- dismissedAt: null,
160
- metadata: { version: 2 },
161
- };
162
- mockDismissibleItem.create.mockResolvedValue(dbItem);
163
-
164
- const result = await adapter.create(item);
165
-
166
- expect(result).toEqual({
167
- id: 'item-456',
168
- userId: 'user-123',
169
- createdAt: new Date('2024-01-15T10:30:00.000Z'),
170
- dismissedAt: undefined,
171
- metadata: { version: 2 },
172
- });
173
- });
174
117
  });
175
118
 
176
119
  describe('update', () => {
@@ -186,7 +129,6 @@ describe('PostgresStorageAdapter', () => {
186
129
  userId: 'user-123',
187
130
  createdAt: new Date('2024-01-15T10:30:00.000Z'),
188
131
  dismissedAt: new Date('2024-01-15T12:00:00.000Z'),
189
- metadata: null,
190
132
  };
191
133
  mockDismissibleItem.update.mockResolvedValue(dbItem);
192
134
 
@@ -197,7 +139,6 @@ describe('PostgresStorageAdapter', () => {
197
139
  userId: 'user-123',
198
140
  createdAt: new Date('2024-01-15T10:30:00.000Z'),
199
141
  dismissedAt: new Date('2024-01-15T12:00:00.000Z'),
200
- metadata: undefined,
201
142
  });
202
143
  expect(mockDismissibleItem.update).toHaveBeenCalledWith({
203
144
  where: {
@@ -208,7 +149,6 @@ describe('PostgresStorageAdapter', () => {
208
149
  },
209
150
  data: {
210
151
  dismissedAt: new Date('2024-01-15T12:00:00.000Z'),
211
- metadata: Prisma.JsonNull,
212
152
  },
213
153
  });
214
154
  expect(mockLogger.debug).toHaveBeenCalledWith('PostgreSQL storage update', {
@@ -216,33 +156,5 @@ describe('PostgresStorageAdapter', () => {
216
156
  itemId: 'item-456',
217
157
  });
218
158
  });
219
-
220
- it('should update an item with metadata', async () => {
221
- const item: DismissibleItemDto = {
222
- id: 'item-456',
223
- userId: 'user-123',
224
- createdAt: new Date('2024-01-15T10:30:00.000Z'),
225
- dismissedAt: new Date('2024-01-15T12:00:00.000Z'),
226
- metadata: { version: 3, updated: true },
227
- };
228
- const dbItem = {
229
- id: 'item-456',
230
- userId: 'user-123',
231
- createdAt: new Date('2024-01-15T10:30:00.000Z'),
232
- dismissedAt: new Date('2024-01-15T12:00:00.000Z'),
233
- metadata: { version: 3, updated: true },
234
- };
235
- mockDismissibleItem.update.mockResolvedValue(dbItem);
236
-
237
- const result = await adapter.update(item);
238
-
239
- expect(result).toEqual({
240
- id: 'item-456',
241
- userId: 'user-123',
242
- createdAt: new Date('2024-01-15T10:30:00.000Z'),
243
- dismissedAt: new Date('2024-01-15T12:00:00.000Z'),
244
- metadata: { version: 3, updated: true },
245
- });
246
- });
247
159
  });
248
160
  });
@@ -1,12 +1,7 @@
1
1
  import { Injectable, Inject } from '@nestjs/common';
2
2
  import { DISMISSIBLE_LOGGER, IDismissibleLogger } from '@dismissible/nestjs-logger';
3
3
  import { IDismissibleStorage } from '@dismissible/nestjs-storage';
4
- import {
5
- BaseMetadata,
6
- DismissibleItemDto,
7
- DismissibleItemFactory,
8
- } from '@dismissible/nestjs-dismissible-item';
9
- import { Prisma } from '../prisma/generated/prisma/client';
4
+ import { DismissibleItemDto, DismissibleItemFactory } from '@dismissible/nestjs-dismissible-item';
10
5
  import { PrismaService } from './prisma.service';
11
6
 
12
7
  /**
@@ -14,16 +9,14 @@ import { PrismaService } from './prisma.service';
14
9
  * Implements IDismissibleStorage for persistent database storage.
15
10
  */
16
11
  @Injectable()
17
- export class PostgresStorageAdapter<
18
- TMetadata extends BaseMetadata = BaseMetadata,
19
- > implements IDismissibleStorage<TMetadata> {
12
+ export class PostgresStorageAdapter implements IDismissibleStorage {
20
13
  constructor(
21
14
  private readonly prisma: PrismaService,
22
15
  @Inject(DISMISSIBLE_LOGGER) private readonly logger: IDismissibleLogger,
23
16
  private readonly itemFactory: DismissibleItemFactory,
24
17
  ) {}
25
18
 
26
- async get(userId: string, itemId: string): Promise<DismissibleItemDto<TMetadata> | null> {
19
+ async get(userId: string, itemId: string): Promise<DismissibleItemDto | null> {
27
20
  this.logger.debug('PostgreSQL storage get', { userId, itemId });
28
21
 
29
22
  const item = await this.prisma.dismissibleItem.findUnique({
@@ -45,7 +38,7 @@ export class PostgresStorageAdapter<
45
38
  return this.mapToDto(item);
46
39
  }
47
40
 
48
- async create(item: DismissibleItemDto<TMetadata>): Promise<DismissibleItemDto<TMetadata>> {
41
+ async create(item: DismissibleItemDto): Promise<DismissibleItemDto> {
49
42
  this.logger.debug('PostgreSQL storage create', { userId: item.userId, itemId: item.id });
50
43
 
51
44
  const created = await this.prisma.dismissibleItem.create({
@@ -54,14 +47,13 @@ export class PostgresStorageAdapter<
54
47
  userId: item.userId,
55
48
  createdAt: item.createdAt,
56
49
  dismissedAt: item.dismissedAt ?? null,
57
- metadata: (item.metadata as Prisma.InputJsonValue) ?? Prisma.JsonNull,
58
50
  },
59
51
  });
60
52
 
61
53
  return this.mapToDto(created);
62
54
  }
63
55
 
64
- async update(item: DismissibleItemDto<TMetadata>): Promise<DismissibleItemDto<TMetadata>> {
56
+ async update(item: DismissibleItemDto): Promise<DismissibleItemDto> {
65
57
  this.logger.debug('PostgreSQL storage update', { userId: item.userId, itemId: item.id });
66
58
 
67
59
  const updated = await this.prisma.dismissibleItem.update({
@@ -73,7 +65,6 @@ export class PostgresStorageAdapter<
73
65
  },
74
66
  data: {
75
67
  dismissedAt: item.dismissedAt ?? null,
76
- metadata: (item.metadata as Prisma.InputJsonValue) ?? Prisma.JsonNull,
77
68
  },
78
69
  });
79
70
 
@@ -88,14 +79,12 @@ export class PostgresStorageAdapter<
88
79
  userId: string;
89
80
  createdAt: Date;
90
81
  dismissedAt: Date | null;
91
- metadata: unknown;
92
- }): DismissibleItemDto<TMetadata> {
82
+ }): DismissibleItemDto {
93
83
  return this.itemFactory.create({
94
84
  id: item.id,
95
85
  userId: item.userId,
96
86
  createdAt: item.createdAt,
97
87
  dismissedAt: item.dismissedAt ?? undefined,
98
- metadata: (item.metadata as TMetadata) ?? undefined,
99
88
  });
100
89
  }
101
90
  }
@@ -46,7 +46,7 @@ export class PostgresStorageModule {
46
46
  useExisting: PostgresStorageAdapter,
47
47
  },
48
48
  ],
49
- exports: [DISMISSIBLE_STORAGE_ADAPTER],
49
+ exports: [DISMISSIBLE_STORAGE_ADAPTER, PrismaService],
50
50
  };
51
51
  }
52
52
 
@@ -73,7 +73,7 @@ export class PostgresStorageModule {
73
73
  useExisting: PostgresStorageAdapter,
74
74
  },
75
75
  ],
76
- exports: [DISMISSIBLE_STORAGE_ADAPTER],
76
+ exports: [DISMISSIBLE_STORAGE_ADAPTER, PrismaService],
77
77
  };
78
78
  }
79
79
  }
@@ -0,0 +1,93 @@
1
+ import { join } from 'path';
2
+
3
+ describe('prisma-config', () => {
4
+ const originalEnv = process.env;
5
+
6
+ beforeEach(() => {
7
+ jest.resetModules();
8
+ process.env = { ...originalEnv };
9
+ delete process.env.DATABASE_URL;
10
+ delete process.env.DISMISSIBLE_POSTGRES_STORAGE_CONNECTION_STRING;
11
+ });
12
+
13
+ afterAll(() => {
14
+ process.env = originalEnv;
15
+ });
16
+
17
+ describe('createPrismaConfig', () => {
18
+ it('should return config with schema path relative to package', async () => {
19
+ const { createPrismaConfig } = await import('./prisma-config');
20
+ const config = createPrismaConfig();
21
+
22
+ expect(config.schema).toContain('schema.prisma');
23
+ expect(config.schema).toMatch(/postgres-storage.*prisma.*schema\.prisma$/);
24
+ });
25
+
26
+ it('should return config with migrations path relative to package', async () => {
27
+ const { createPrismaConfig } = await import('./prisma-config');
28
+ const config = createPrismaConfig();
29
+
30
+ expect(config.migrations.path).toContain('migrations');
31
+ expect(config.migrations.path).toMatch(/postgres-storage.*prisma.*migrations$/);
32
+ });
33
+
34
+ it('should use DATABASE_URL when set', async () => {
35
+ process.env.DATABASE_URL = 'postgres://test:test@localhost/test';
36
+
37
+ const { createPrismaConfig } = await import('./prisma-config');
38
+ const config = createPrismaConfig();
39
+
40
+ expect(config.datasource.url).toBe('postgres://test:test@localhost/test');
41
+ });
42
+
43
+ it('should fall back to DISMISSIBLE_POSTGRES_STORAGE_CONNECTION_STRING when DATABASE_URL is not set', async () => {
44
+ process.env.DISMISSIBLE_POSTGRES_STORAGE_CONNECTION_STRING =
45
+ 'postgres://fallback:fallback@localhost/fallback';
46
+
47
+ const { createPrismaConfig } = await import('./prisma-config');
48
+ const config = createPrismaConfig();
49
+
50
+ expect(config.datasource.url).toBe('postgres://fallback:fallback@localhost/fallback');
51
+ });
52
+
53
+ it('should prefer DATABASE_URL over DISMISSIBLE_POSTGRES_STORAGE_CONNECTION_STRING', async () => {
54
+ process.env.DATABASE_URL = 'postgres://primary:primary@localhost/primary';
55
+ process.env.DISMISSIBLE_POSTGRES_STORAGE_CONNECTION_STRING =
56
+ 'postgres://fallback:fallback@localhost/fallback';
57
+
58
+ const { createPrismaConfig } = await import('./prisma-config');
59
+ const config = createPrismaConfig();
60
+
61
+ expect(config.datasource.url).toBe('postgres://primary:primary@localhost/primary');
62
+ });
63
+
64
+ it('should return empty string when no database URL is set', async () => {
65
+ const { createPrismaConfig } = await import('./prisma-config');
66
+ const config = createPrismaConfig();
67
+
68
+ expect(config.datasource.url).toBe('');
69
+ });
70
+ });
71
+
72
+ describe('basePrismaConfig', () => {
73
+ it('should be exported and have the expected structure', async () => {
74
+ const { basePrismaConfig } = await import('./prisma-config');
75
+
76
+ expect(basePrismaConfig).toBeDefined();
77
+ expect(basePrismaConfig).toHaveProperty('schema');
78
+ expect(basePrismaConfig).toHaveProperty('migrations');
79
+ expect(basePrismaConfig).toHaveProperty('datasource');
80
+ expect(basePrismaConfig.migrations).toHaveProperty('path');
81
+ expect(basePrismaConfig.datasource).toHaveProperty('url');
82
+ });
83
+
84
+ it('should have schema and migrations paths pointing to the same prisma directory', async () => {
85
+ const { basePrismaConfig } = await import('./prisma-config');
86
+
87
+ const schemaDir = join(basePrismaConfig.schema, '..');
88
+ const migrationsDir = join(basePrismaConfig.migrations.path, '..');
89
+
90
+ expect(schemaDir).toBe(migrationsDir);
91
+ });
92
+ });
93
+ });
@@ -0,0 +1,63 @@
1
+ import { join } from 'path';
2
+ import { getPrismaSchemaPath } from './schema-path';
3
+
4
+ /**
5
+ * Creates a Prisma configuration object with paths resolved relative to
6
+ * the @dismissible/nestjs-postgres-storage package.
7
+ *
8
+ * @returns Prisma configuration object suitable for use with defineConfig()
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * // prisma.config.mjs
13
+ * import { defineConfig } from 'prisma/config';
14
+ * import { basePrismaConfig } from '@dismissible/nestjs-postgres-storage';
15
+ *
16
+ * export default defineConfig(basePrismaConfig);
17
+ * ```
18
+ */
19
+ export function createPrismaConfig() {
20
+ const prismaDir = join(getPrismaSchemaPath(), '..');
21
+ return {
22
+ schema: join(prismaDir, 'schema.prisma'),
23
+ migrations: {
24
+ path: join(prismaDir, 'migrations'),
25
+ },
26
+ datasource: {
27
+ url:
28
+ process.env.DATABASE_URL ??
29
+ process.env.DISMISSIBLE_POSTGRES_STORAGE_CONNECTION_STRING ??
30
+ '',
31
+ },
32
+ };
33
+ }
34
+
35
+ /**
36
+ * Base Prisma configuration for @dismissible/nestjs-postgres-storage.
37
+ *
38
+ * Use this with Prisma's defineConfig() to create your prisma.config.mjs:
39
+ *
40
+ * @example
41
+ * ```javascript
42
+ * // prisma.config.mjs
43
+ * import { defineConfig } from 'prisma/config';
44
+ * import { basePrismaConfig } from '@dismissible/nestjs-postgres-storage';
45
+ *
46
+ * export default defineConfig(basePrismaConfig);
47
+ * ```
48
+ *
49
+ * @example Extending the config
50
+ * ```javascript
51
+ * import { defineConfig } from 'prisma/config';
52
+ * import { basePrismaConfig } from '@dismissible/nestjs-postgres-storage';
53
+ *
54
+ * export default defineConfig({
55
+ * ...basePrismaConfig,
56
+ * migrations: {
57
+ * ...basePrismaConfig.migrations,
58
+ * seed: 'tsx prisma/seed.ts',
59
+ * },
60
+ * });
61
+ * ```
62
+ */
63
+ export const basePrismaConfig = createPrismaConfig();
@@ -0,0 +1,125 @@
1
+ import { mock } from 'ts-jest-mocker';
2
+ import { PrismaService } from './prisma.service';
3
+ import { PostgresStorageConfig } from './postgres-storage.config';
4
+ import { IDismissibleLogger } from '@dismissible/nestjs-logger';
5
+
6
+ const mockPoolInstance = {};
7
+ const mockPrismaPgInstance = {};
8
+
9
+ jest.mock('pg', () => ({
10
+ Pool: jest.fn().mockImplementation(() => mockPoolInstance),
11
+ }));
12
+
13
+ jest.mock('@prisma/adapter-pg', () => ({
14
+ PrismaPg: jest.fn().mockImplementation(() => mockPrismaPgInstance),
15
+ }));
16
+
17
+ const mockPrismaClientMethods = {
18
+ $connect: jest.fn(),
19
+ $disconnect: jest.fn(),
20
+ $queryRaw: jest.fn(),
21
+ };
22
+
23
+ jest.mock('../prisma/generated/prisma/client', () => {
24
+ class MockPrismaClient {
25
+ $connect = jest.fn();
26
+ $disconnect = jest.fn();
27
+ $queryRaw = jest.fn();
28
+ }
29
+ return {
30
+ PrismaClient: MockPrismaClient,
31
+ };
32
+ });
33
+
34
+ describe('PrismaService', () => {
35
+ let service: PrismaService;
36
+ let mockConfig: PostgresStorageConfig;
37
+ let mockLogger: jest.Mocked<IDismissibleLogger>;
38
+
39
+ beforeEach(() => {
40
+ mockConfig = {
41
+ connectionString: 'postgresql://user:password@localhost:5432/testdb',
42
+ };
43
+ mockLogger = mock<IDismissibleLogger>({ failIfMockNotProvided: false });
44
+
45
+ jest.clearAllMocks();
46
+ mockPrismaClientMethods.$connect.mockClear();
47
+ mockPrismaClientMethods.$disconnect.mockClear();
48
+ mockPrismaClientMethods.$queryRaw.mockClear();
49
+ });
50
+
51
+ describe('constructor', () => {
52
+ it('should create PrismaService with pool and adapter', () => {
53
+ const { Pool } = require('pg');
54
+ const { PrismaPg } = require('@prisma/adapter-pg');
55
+
56
+ service = new PrismaService(mockConfig, mockLogger);
57
+
58
+ expect(service).toBeDefined();
59
+ expect(Pool).toHaveBeenCalledWith({
60
+ connectionString: mockConfig.connectionString,
61
+ });
62
+ expect(PrismaPg).toHaveBeenCalledWith(mockPoolInstance);
63
+ });
64
+ });
65
+
66
+ describe('onModuleInit', () => {
67
+ it('should connect to database and verify connection', async () => {
68
+ service = new PrismaService(mockConfig, mockLogger);
69
+ (service as any).$connect.mockResolvedValue(undefined);
70
+ (service as any).$queryRaw.mockResolvedValue([{ '?column?': 1 }]);
71
+
72
+ await service.onModuleInit();
73
+
74
+ expect(mockLogger.debug).toHaveBeenCalledWith('Connecting to PostgreSQL database');
75
+ expect((service as any).$connect).toHaveBeenCalled();
76
+ expect((service as any).$queryRaw).toHaveBeenCalled();
77
+ expect(mockLogger.debug).toHaveBeenCalledWith('Connected to PostgreSQL database');
78
+ });
79
+
80
+ it('should throw error when connection fails', async () => {
81
+ service = new PrismaService(mockConfig, mockLogger);
82
+ const connectError = new Error('Connection refused');
83
+ (service as any).$connect = jest.fn().mockRejectedValue(connectError);
84
+
85
+ await expect(service.onModuleInit()).rejects.toThrow('Connection refused');
86
+ });
87
+
88
+ it('should throw error when query verification fails', async () => {
89
+ service = new PrismaService(mockConfig, mockLogger);
90
+ (service as any).$connect.mockResolvedValue(undefined);
91
+ (service as any).$queryRaw.mockRejectedValue(new Error('Query failed'));
92
+
93
+ await expect(service.onModuleInit()).rejects.toThrow(
94
+ 'Database connection failed: Query failed. Ensure PostgreSQL is running and DISMISSIBLE_POSTGRES_STORAGE_CONNECTION_STRING is configured correctly.',
95
+ );
96
+ expect(mockLogger.error).toHaveBeenCalledWith(
97
+ 'Failed to connect to PostgreSQL database',
98
+ expect.any(Error),
99
+ );
100
+ });
101
+
102
+ it('should handle non-Error objects in catch block', async () => {
103
+ service = new PrismaService(mockConfig, mockLogger);
104
+ (service as any).$connect.mockResolvedValue(undefined);
105
+ (service as any).$queryRaw.mockRejectedValue('String error');
106
+
107
+ await expect(service.onModuleInit()).rejects.toThrow(
108
+ 'Database connection failed: Unknown error. Ensure PostgreSQL is running and DISMISSIBLE_POSTGRES_STORAGE_CONNECTION_STRING is configured correctly.',
109
+ );
110
+ });
111
+ });
112
+
113
+ describe('onModuleDestroy', () => {
114
+ it('should disconnect from database', async () => {
115
+ service = new PrismaService(mockConfig, mockLogger);
116
+ (service as any).$disconnect.mockResolvedValue(undefined);
117
+
118
+ await service.onModuleDestroy();
119
+
120
+ expect(mockLogger.debug).toHaveBeenCalledWith('Disconnecting from PostgreSQL database');
121
+ expect((service as any).$disconnect).toHaveBeenCalled();
122
+ expect(mockLogger.debug).toHaveBeenCalledWith('Disconnected from PostgreSQL database');
123
+ });
124
+ });
125
+ });
@@ -23,7 +23,17 @@ export class PrismaService extends PrismaClient implements OnModuleInit, OnModul
23
23
  async onModuleInit(): Promise<void> {
24
24
  this.logger.debug('Connecting to PostgreSQL database');
25
25
  await this.$connect();
26
- this.logger.debug('Connected to PostgreSQL database');
26
+
27
+ try {
28
+ await this.$queryRaw`SELECT 1`;
29
+ this.logger.debug('Connected to PostgreSQL database');
30
+ } catch (error) {
31
+ this.logger.error('Failed to connect to PostgreSQL database', error);
32
+ throw new Error(
33
+ `Database connection failed: ${error instanceof Error ? error.message : 'Unknown error'}. ` +
34
+ 'Ensure PostgreSQL is running and DISMISSIBLE_POSTGRES_STORAGE_CONNECTION_STRING is configured correctly.',
35
+ );
36
+ }
27
37
  }
28
38
 
29
39
  async onModuleDestroy(): Promise<void> {
@@ -0,0 +1,22 @@
1
+ import { getPrismaSchemaPath } from './schema-path';
2
+ import { join } from 'path';
3
+
4
+ describe('schema-path', () => {
5
+ describe('getPrismaSchemaPath', () => {
6
+ it('should return the absolute path to the Prisma schema file', () => {
7
+ const schemaPath = getPrismaSchemaPath();
8
+
9
+ expect(schemaPath).toBeDefined();
10
+ expect(typeof schemaPath).toBe('string');
11
+ expect(schemaPath).toContain('prisma');
12
+ expect(schemaPath).toContain('schema.prisma');
13
+ expect(schemaPath).toBe(join(__dirname, '..', 'prisma', 'schema.prisma'));
14
+ });
15
+
16
+ it('should return a valid path structure', () => {
17
+ const schemaPath = getPrismaSchemaPath();
18
+
19
+ expect(schemaPath).toMatch(/^(\/|[A-Z]:\\)/);
20
+ });
21
+ });
22
+ });