-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathintegration.test.ts
More file actions
343 lines (294 loc) · 9.74 KB
/
integration.test.ts
File metadata and controls
343 lines (294 loc) · 9.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
/**
* Next.js Cache Plugin 統合テスト
*
* SQLite(インメモリ)を使用して、プラグインの動作をテストします。
* 外部データベースへの依存なしにテストを実行できます。
*/
import { type ClientContract, ZenStackClient } from '@zenstackhq/orm'
import { SqliteDialect } from '@zenstackhq/orm/dialects/sqlite'
import Database from 'better-sqlite3'
import {
afterAll,
beforeAll,
beforeEach,
describe,
expect,
it,
vi,
} from 'vitest'
import { createNextjsCachePlugin } from './index'
// next/cache のモック
const mockCacheTag = vi.fn()
const mockCacheLife = vi.fn()
const mockUpdateTag = vi.fn()
const mockUnstableCache = vi.fn(
(fn: () => Promise<unknown>, _keys: string[], _options?: object) => {
// unstable_cache は関数を返す
return fn
},
)
vi.mock('next/cache', () => ({
cacheLife: (...args: unknown[]) => mockCacheLife(...args),
cacheTag: (...args: unknown[]) => mockCacheTag(...args),
unstable_cache: (
fn: () => Promise<unknown>,
keys: string[],
options?: object,
) => mockUnstableCache(fn, keys, options),
updateTag: (...args: unknown[]) => mockUpdateTag(...args),
}))
// テスト用のユニークIDを生成
// biome-ignore lint/style/noMagicNumbers: テスト用ID生成
const testId = () => `test-${Date.now()}-${Math.random().toString(36).slice(2)}`
// テスト用のスキーマ定義(SQLite用に簡略化)
import { ExpressionUtils } from '@zenstackhq/orm/schema'
const testSchema = {
models: {
Session: {
fields: {
id: { id: true, name: 'id', type: 'String' },
token: { name: 'token', type: 'String' },
userId: { name: 'userId', type: 'String' },
},
idFields: ['id'],
name: 'Session',
uniqueFields: { id: { type: 'String' } },
},
User: {
fields: {
createdAt: {
default: ExpressionUtils.call('now'),
name: 'createdAt',
optional: true,
type: 'DateTime',
},
email: { name: 'email', type: 'String' },
id: { id: true, name: 'id', type: 'String' },
name: { name: 'name', type: 'String' },
updatedAt: {
default: ExpressionUtils.call('now'),
name: 'updatedAt',
optional: true,
type: 'DateTime',
updatedAt: true,
},
},
idFields: ['id'],
name: 'User',
uniqueFields: { email: { type: 'String' }, id: { type: 'String' } },
},
},
provider: { type: 'sqlite' as const },
}
describe('Next.js Cache Plugin 統合テスト', () => {
let sqlite: Database.Database
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// biome-ignore lint/suspicious/noExplicitAny: テストコードでのモック型定義
let db: ClientContract<any>
beforeAll(() => {
// インメモリ SQLite データベースを作成
sqlite = new Database(':memory:')
// テーブルを作成
sqlite.exec(`
CREATE TABLE user (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
createdAt TEXT DEFAULT (datetime('now')),
updatedAt TEXT DEFAULT (datetime('now'))
);
CREATE TABLE session (
id TEXT PRIMARY KEY,
token TEXT NOT NULL,
userId TEXT NOT NULL
);
`)
})
beforeEach(() => {
vi.clearAllMocks()
// ZenStack クライアントを作成
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// biome-ignore lint/suspicious/noExplicitAny: テストスキーマの型キャスト
const baseDb = new ZenStackClient(testSchema as any, {
dialect: new SqliteDialect({ database: sqlite }),
})
// プラグインを適用
db = baseDb.$use(
createNextjsCachePlugin({
defaultCacheLife: 'hours',
excludeModels: ['Session'],
}),
)
})
afterAll(() => {
sqlite?.close()
})
describe('User CRUD操作', () => {
describe('Create', () => {
it('ユーザーを作成できる', async () => {
const userId = testId()
const user = await db.user.create({
data: {
email: `test-${userId}@example.com`,
id: userId,
name: 'Test User',
},
})
expect(user).toMatchObject({
email: `test-${userId}@example.com`,
id: userId,
name: 'Test User',
})
})
it('作成時にはキャッシュタグを設定しない', async () => {
const userId = testId()
await db.user.create({
data: {
email: `test-${userId}@example.com`,
id: userId,
name: 'Test User',
},
})
// create は書き込み操作なのでcacheTagは呼ばれない
expect(mockCacheTag).not.toHaveBeenCalled()
})
})
describe('Read', () => {
let testUserId: string
beforeEach(async () => {
// テストデータを事前に作成
testUserId = testId()
await db.user.create({
data: {
email: `read-test-${testUserId}@example.com`,
id: testUserId,
name: 'Read Test User',
},
})
vi.clearAllMocks()
})
it('findUniqueでユーザーを取得できる', async () => {
const user = await db.user.findUnique({
where: { id: testUserId },
})
expect(user).toMatchObject({
id: testUserId,
name: 'Read Test User',
})
})
it('findUnique時にキャッシュが設定される', async () => {
await db.user.findUnique({
where: { id: testUserId },
})
// unstable_cache が呼ばれて、タグ付きでキャッシュされることを確認
expect(mockUnstableCache).toHaveBeenCalled()
const lastCall = mockUnstableCache.mock.calls[0]
const options = lastCall[2] as { tags: string[]; revalidate: number }
expect(options.tags).toContain('user:list')
expect(options.tags).toContain(`user:${testUserId}`)
expect(options.revalidate).toBe(3600) // hours = 3600秒
})
it('findManyでユーザーを取得できる', async () => {
const users = await db.user.findMany({
where: { id: testUserId },
})
expect(users).toHaveLength(1)
expect(users[0]).toMatchObject({
id: testUserId,
name: 'Read Test User',
})
})
it('findMany時にキャッシュが設定される', async () => {
await db.user.findMany({
where: { id: testUserId },
})
// unstable_cache が呼ばれて、タグ付きでキャッシュされることを確認
expect(mockUnstableCache).toHaveBeenCalled()
const lastCall = mockUnstableCache.mock.calls[0]
const options = lastCall[2] as { tags: string[]; revalidate: number }
expect(options.tags).toContain('user:list')
expect(options.revalidate).toBe(3600) // hours = 3600秒
})
it('countでユーザー数を取得できる', async () => {
const count = await db.user.count({
where: { id: testUserId },
})
expect(count).toBe(1)
})
it('Date型がキャッシュ後も維持される', async () => {
const user = await db.user.findUnique({
where: { id: testUserId },
})
// キャッシュ後もDate型が維持されている
expect(user?.createdAt).toBeDefined()
expect(user?.createdAt).toBeInstanceOf(Date)
})
})
describe('Update', () => {
let testUserId: string
beforeEach(async () => {
testUserId = testId()
await db.user.create({
data: {
email: `update-test-${testUserId}@example.com`,
id: testUserId,
name: 'Update Test User',
},
})
vi.clearAllMocks()
})
it('ユーザーを更新できる', async () => {
const updated = await db.user.update({
data: { name: 'Updated Name' },
where: { id: testUserId },
})
expect(updated.name).toBe('Updated Name')
})
it('更新時にはキャッシュタグを設定しない', async () => {
await db.user.update({
data: { name: 'Updated Name' },
where: { id: testUserId },
})
// update は書き込み操作なのでcacheTagは呼ばれない
expect(mockCacheTag).not.toHaveBeenCalled()
})
})
describe('Delete', () => {
it('ユーザーを削除できる', async () => {
const testUserId = testId()
await db.user.create({
data: {
email: `delete-test-${testUserId}@example.com`,
id: testUserId,
name: 'Delete Test User',
},
})
await db.user.delete({
where: { id: testUserId },
})
const user = await db.user.findUnique({
where: { id: testUserId },
})
expect(user).toBeNull()
})
})
})
describe('除外モデル(Session)の動作', () => {
it('除外モデルはキャッシュタグを設定しない', async () => {
// Session モデルの読み取りを試みる
// 実際のデータがなくても findMany は空配列を返す
await db.session.findMany({
take: 1,
})
// Session は除外モデルなのでcacheTagは呼ばれない
expect(mockCacheTag).not.toHaveBeenCalled()
})
})
describe('キャッシュ無効化', () => {
it('ユーザー作成後にonEntityMutationが呼ばれる設定になっている', () => {
const plugin = createNextjsCachePlugin()
expect(plugin.onEntityMutation).toBeDefined()
expect(plugin.onEntityMutation?.afterEntityMutation).toBeDefined()
})
})
})