@aiquants/daily-report 0.1.1
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.
- package/LICENSE +21 -0
- package/README.md +150 -0
- package/dist/client.d.mts +449 -0
- package/dist/client.d.ts +449 -0
- package/dist/client.js +7 -0
- package/dist/client.js.map +1 -0
- package/dist/client.mjs +7 -0
- package/dist/client.mjs.map +1 -0
- package/dist/index.d.mts +42 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2 -0
- package/dist/index.mjs.map +1 -0
- package/dist/logger-D3krZrNK.d.mts +29 -0
- package/dist/logger-D3krZrNK.d.ts +29 -0
- package/dist/server.d.mts +1515 -0
- package/dist/server.d.ts +1515 -0
- package/dist/server.js +10 -0
- package/dist/server.js.map +1 -0
- package/dist/server.mjs +10 -0
- package/dist/server.mjs.map +1 -0
- package/dist/sse-schema-CK7cUnEo.d.ts +1986 -0
- package/dist/sse-schema-yl5AaSsj.d.mts +1986 -0
- package/dist/types-CVhwLhSN.d.mts +76 -0
- package/dist/types-CVhwLhSN.d.ts +76 -0
- package/package.json +108 -0
- package/src/client/components/business-day-thumb-overlay.tsx +19 -0
- package/src/client/components/daily-report-comment-item.tsx +81 -0
- package/src/client/components/daily-report-comment-section.tsx +166 -0
- package/src/client/components/daily-report-detail-list.tsx +676 -0
- package/src/client/components/daily-report-edit-form.tsx +81 -0
- package/src/client/components/daily-report-list.tsx +1024 -0
- package/src/client/components/daily-report-page.tsx +147 -0
- package/src/client/components/daily-report-resolved-content.tsx +139 -0
- package/src/client/components/unread-indicator.tsx +13 -0
- package/src/client/config-context.tsx +129 -0
- package/src/client/contexts/daily-report-action-context.tsx +910 -0
- package/src/client/hooks/use-daily-report-comments.ts +73 -0
- package/src/client/hooks/use-daily-report-sse-connection.ts +86 -0
- package/src/client/hooks/use-daily-report.spec.ts +155 -0
- package/src/client/hooks/use-daily-report.ts +426 -0
- package/src/client/hooks/use-dynamic-viewport-height.ts +127 -0
- package/src/client/route-helpers.ts +76 -0
- package/src/client/ui/button.tsx +42 -0
- package/src/client/ui/cn.ts +14 -0
- package/src/client/ui/input.tsx +21 -0
- package/src/client/ui/label.tsx +16 -0
- package/src/client/ui/switch.tsx +19 -0
- package/src/client/ui/tabs.tsx +40 -0
- package/src/client/ui/textarea.tsx +19 -0
- package/src/client/utils/constants.ts +29 -0
- package/src/client.ts +21 -0
- package/src/index.ts +10 -0
- package/src/server/cache.ts +165 -0
- package/src/server/etag.ts +18 -0
- package/src/server/external-source.ts +60 -0
- package/src/server/handlers.ts +543 -0
- package/src/server/ports.ts +68 -0
- package/src/server/response.ts +37 -0
- package/src/server/schema.ts +266 -0
- package/src/server/service.spec.ts +97 -0
- package/src/server/service.ts +1308 -0
- package/src/server/sse-reader.spec.ts +55 -0
- package/src/server/sse-reader.ts +223 -0
- package/src/server.ts +83 -0
- package/src/shared/business-date.spec.ts +61 -0
- package/src/shared/business-date.ts +84 -0
- package/src/shared/comment-adapter.ts +78 -0
- package/src/shared/logger.ts +47 -0
- package/src/shared/sse-schema.ts +147 -0
- package/src/shared/text-utils.ts +57 -0
- package/src/shared/types.ts +76 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural table types + drizzle (mssql) schema factory for the daily-report tables.
|
|
3
|
+
* 日報テーブル群の構造的テーブル型と drizzle (mssql) スキーマファクトリ。
|
|
4
|
+
*
|
|
5
|
+
* 既存アプリは自前のモデル定義をそのまま `DailyReportTables` として注入できる (構造互換)。
|
|
6
|
+
* 新規プロジェクトは `defineDailyReportSchema()` で同一構造のテーブル定義を生成できる。
|
|
7
|
+
*/
|
|
8
|
+
import { desc, sql } from "drizzle-orm"
|
|
9
|
+
import { type AnyMsSqlColumn, bigint, bit, date, datetime2, foreignKey, index, int, mssqlSchema, nvarchar, primaryKey } from "drizzle-orm/mssql-core"
|
|
10
|
+
|
|
11
|
+
/** 注入する外部ユーザーテーブルの最小形 (id / display_name)。 */
|
|
12
|
+
export type DailyReportUserTable = { id: AnyMsSqlColumn; displayName: AnyMsSqlColumn }
|
|
13
|
+
|
|
14
|
+
/** DailyReportHub テーブルの構造形。 */
|
|
15
|
+
export type DailyReportHubTable = {
|
|
16
|
+
id: AnyMsSqlColumn
|
|
17
|
+
sourceType: AnyMsSqlColumn
|
|
18
|
+
sourceId: AnyMsSqlColumn
|
|
19
|
+
sourceIdNum: AnyMsSqlColumn
|
|
20
|
+
businessDate: AnyMsSqlColumn
|
|
21
|
+
userId: AnyMsSqlColumn
|
|
22
|
+
title: AnyMsSqlColumn
|
|
23
|
+
summary: AnyMsSqlColumn
|
|
24
|
+
createdAt: AnyMsSqlColumn
|
|
25
|
+
createdBy: AnyMsSqlColumn
|
|
26
|
+
updatedAt: AnyMsSqlColumn
|
|
27
|
+
updatedBy: AnyMsSqlColumn
|
|
28
|
+
deletedAt: AnyMsSqlColumn
|
|
29
|
+
deletedBy: AnyMsSqlColumn
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** DailyReportInternal テーブルの構造形。 */
|
|
33
|
+
export type DailyReportInternalTable = {
|
|
34
|
+
hubId: AnyMsSqlColumn
|
|
35
|
+
body: AnyMsSqlColumn
|
|
36
|
+
metadata: AnyMsSqlColumn
|
|
37
|
+
createdAt: AnyMsSqlColumn
|
|
38
|
+
createdBy: AnyMsSqlColumn
|
|
39
|
+
updatedAt: AnyMsSqlColumn
|
|
40
|
+
updatedBy: AnyMsSqlColumn
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** DailyReportComment テーブルの構造形。 */
|
|
44
|
+
export type DailyReportCommentTable = {
|
|
45
|
+
id: AnyMsSqlColumn
|
|
46
|
+
hubId: AnyMsSqlColumn
|
|
47
|
+
userId: AnyMsSqlColumn
|
|
48
|
+
body: AnyMsSqlColumn
|
|
49
|
+
createdAt: AnyMsSqlColumn
|
|
50
|
+
createdBy: AnyMsSqlColumn
|
|
51
|
+
updatedAt: AnyMsSqlColumn
|
|
52
|
+
updatedBy: AnyMsSqlColumn
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** DailyReportLabel テーブルの構造形。 */
|
|
56
|
+
export type DailyReportLabelTable = {
|
|
57
|
+
id: AnyMsSqlColumn
|
|
58
|
+
userId: AnyMsSqlColumn
|
|
59
|
+
name: AnyMsSqlColumn
|
|
60
|
+
color: AnyMsSqlColumn
|
|
61
|
+
sortOrder: AnyMsSqlColumn
|
|
62
|
+
createdAt: AnyMsSqlColumn
|
|
63
|
+
createdBy: AnyMsSqlColumn
|
|
64
|
+
updatedAt: AnyMsSqlColumn
|
|
65
|
+
updatedBy: AnyMsSqlColumn
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** DailyReportHub_Label 中間テーブルの構造形。 */
|
|
69
|
+
export type DailyReportHubLabelTable = {
|
|
70
|
+
hubId: AnyMsSqlColumn
|
|
71
|
+
labelId: AnyMsSqlColumn
|
|
72
|
+
createdAt: AnyMsSqlColumn
|
|
73
|
+
createdBy: AnyMsSqlColumn
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** DailyReportUserStatus テーブルの構造形。 */
|
|
77
|
+
export type DailyReportUserStatusTable = {
|
|
78
|
+
hubId: AnyMsSqlColumn
|
|
79
|
+
userId: AnyMsSqlColumn
|
|
80
|
+
isRead: AnyMsSqlColumn
|
|
81
|
+
isStarred: AnyMsSqlColumn
|
|
82
|
+
createdAt: AnyMsSqlColumn
|
|
83
|
+
createdBy: AnyMsSqlColumn
|
|
84
|
+
updatedAt: AnyMsSqlColumn
|
|
85
|
+
updatedBy: AnyMsSqlColumn
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** サービスへ注入するテーブル一式 (アプリ既存モデルまたは本ファクトリ生成物)。 */
|
|
89
|
+
export type DailyReportTables = {
|
|
90
|
+
hub: DailyReportHubTable
|
|
91
|
+
internal: DailyReportInternalTable
|
|
92
|
+
comment: DailyReportCommentTable
|
|
93
|
+
label: DailyReportLabelTable
|
|
94
|
+
hubLabel: DailyReportHubLabelTable
|
|
95
|
+
userStatus: DailyReportUserStatusTable
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Generic factory: build the six daily-report tables under an arbitrary schema name.
|
|
100
|
+
* 任意のスキーマ名の下に日報 6 テーブルを生成する汎用ファクトリ。
|
|
101
|
+
*
|
|
102
|
+
* 制約・索引名は `${schemaName}_<Table>_...` 規約で生成する。
|
|
103
|
+
* ユーザーテーブルへの FK は注入された `userTable` を参照する。
|
|
104
|
+
*/
|
|
105
|
+
export function defineDailyReportSchema<S extends string>(schemaName: S, opts: { userTable: DailyReportUserTable }) {
|
|
106
|
+
const s = mssqlSchema(schemaName)
|
|
107
|
+
const users = opts.userTable
|
|
108
|
+
|
|
109
|
+
const hub = s.table(
|
|
110
|
+
"DailyReportHub",
|
|
111
|
+
{
|
|
112
|
+
id: bigint("id", { mode: "number" }).identity().notNull(),
|
|
113
|
+
sourceType: nvarchar("source_type", { length: 20 }).notNull(),
|
|
114
|
+
sourceId: nvarchar("source_id", { length: 100 }).notNull(),
|
|
115
|
+
sourceIdNum: bigint("source_id_num", { mode: "number" }).generatedAlwaysAs(sql`TRY_CAST(source_id AS BIGINT)`),
|
|
116
|
+
businessDate: date("business_date"),
|
|
117
|
+
userId: bigint("user_id", { mode: "number" }),
|
|
118
|
+
title: nvarchar("title", { length: 200 }),
|
|
119
|
+
summary: nvarchar("summary", { length: "max" }),
|
|
120
|
+
createdAt: datetime2("created_at").notNull(),
|
|
121
|
+
createdBy: nvarchar("created_by", { length: 50 }).notNull(),
|
|
122
|
+
updatedAt: datetime2("updated_at").notNull(),
|
|
123
|
+
updatedBy: nvarchar("updated_by", { length: 50 }).notNull(),
|
|
124
|
+
deletedAt: datetime2("deleted_at"),
|
|
125
|
+
deletedBy: nvarchar("deleted_by", { length: 50 }),
|
|
126
|
+
},
|
|
127
|
+
(table) => [
|
|
128
|
+
primaryKey({ name: `${schemaName}_DailyReportHub_pk`, columns: [table.id] }),
|
|
129
|
+
foreignKey({
|
|
130
|
+
name: `${schemaName}_DailyReportHub_user_id_fk`,
|
|
131
|
+
columns: [table.userId],
|
|
132
|
+
foreignColumns: [users.id],
|
|
133
|
+
}),
|
|
134
|
+
index(`${schemaName}_DailyReportHub_business_date_index`).on(table.businessDate),
|
|
135
|
+
index(`${schemaName}_DailyReportHub_updated_at_index`).on(table.updatedAt),
|
|
136
|
+
index(`${schemaName}_DailyReportHub_source_index`).on(table.sourceType, table.sourceId),
|
|
137
|
+
index(`${schemaName}_DailyReportHub_user_id_index`).on(table.userId),
|
|
138
|
+
index(`${schemaName}_DailyReportHub_business_date_id_index`).on(desc(table.businessDate), desc(table.id)),
|
|
139
|
+
index(`${schemaName}_DailyReportHub_deleted_at_index`).on(table.deletedAt),
|
|
140
|
+
],
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
const internal = s.table(
|
|
144
|
+
"DailyReportInternal",
|
|
145
|
+
{
|
|
146
|
+
hubId: bigint("hub_id", { mode: "number" }).notNull(),
|
|
147
|
+
body: nvarchar("body", { length: "max" }),
|
|
148
|
+
metadata: nvarchar("metadata", { length: "max" }),
|
|
149
|
+
createdAt: datetime2("created_at").notNull(),
|
|
150
|
+
createdBy: nvarchar("created_by", { length: 50 }).notNull(),
|
|
151
|
+
updatedAt: datetime2("updated_at").notNull(),
|
|
152
|
+
updatedBy: nvarchar("updated_by", { length: 50 }).notNull(),
|
|
153
|
+
},
|
|
154
|
+
(table) => [
|
|
155
|
+
primaryKey({ name: `${schemaName}_DailyReportInternal_pk`, columns: [table.hubId] }),
|
|
156
|
+
foreignKey({
|
|
157
|
+
name: `${schemaName}_DailyReportInternal_hub_id_fk`,
|
|
158
|
+
columns: [table.hubId],
|
|
159
|
+
foreignColumns: [hub.id],
|
|
160
|
+
}),
|
|
161
|
+
],
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
const comment = s.table(
|
|
165
|
+
"DailyReportComment",
|
|
166
|
+
{
|
|
167
|
+
id: bigint("id", { mode: "number" }).identity().notNull(),
|
|
168
|
+
hubId: bigint("hub_id", { mode: "number" }).notNull(),
|
|
169
|
+
userId: bigint("user_id", { mode: "number" }).notNull(),
|
|
170
|
+
body: nvarchar("body", { length: "max" }).notNull(),
|
|
171
|
+
createdAt: datetime2("created_at").notNull(),
|
|
172
|
+
createdBy: nvarchar("created_by", { length: 50 }).notNull(),
|
|
173
|
+
updatedAt: datetime2("updated_at").notNull(),
|
|
174
|
+
updatedBy: nvarchar("updated_by", { length: 50 }).notNull(),
|
|
175
|
+
},
|
|
176
|
+
(table) => [
|
|
177
|
+
primaryKey({ name: `${schemaName}_DailyReportComment_pk`, columns: [table.id] }),
|
|
178
|
+
foreignKey({
|
|
179
|
+
name: `${schemaName}_DailyReportComment_hub_id_fk`,
|
|
180
|
+
columns: [table.hubId],
|
|
181
|
+
foreignColumns: [hub.id],
|
|
182
|
+
}),
|
|
183
|
+
foreignKey({
|
|
184
|
+
name: `${schemaName}_DailyReportComment_user_id_fk`,
|
|
185
|
+
columns: [table.userId],
|
|
186
|
+
foreignColumns: [users.id],
|
|
187
|
+
}),
|
|
188
|
+
index(`${schemaName}_DailyReportComment_hub_id_index`).on(table.hubId),
|
|
189
|
+
],
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
const label = s.table(
|
|
193
|
+
"DailyReportLabel",
|
|
194
|
+
{
|
|
195
|
+
id: bigint("id", { mode: "number" }).identity().notNull(),
|
|
196
|
+
userId: bigint("user_id", { mode: "number" }),
|
|
197
|
+
name: nvarchar("name", { length: 50 }).notNull(),
|
|
198
|
+
color: nvarchar("color", { length: 20 }),
|
|
199
|
+
sortOrder: int("sort_order"),
|
|
200
|
+
createdAt: datetime2("created_at").notNull(),
|
|
201
|
+
createdBy: nvarchar("created_by", { length: 50 }).notNull(),
|
|
202
|
+
updatedAt: datetime2("updated_at").notNull(),
|
|
203
|
+
updatedBy: nvarchar("updated_by", { length: 50 }).notNull(),
|
|
204
|
+
},
|
|
205
|
+
(table) => [
|
|
206
|
+
primaryKey({ name: `${schemaName}_DailyReportLabel_pk`, columns: [table.id] }),
|
|
207
|
+
foreignKey({
|
|
208
|
+
name: `${schemaName}_DailyReportLabel_user_id_fk`,
|
|
209
|
+
columns: [table.userId],
|
|
210
|
+
foreignColumns: [users.id],
|
|
211
|
+
}),
|
|
212
|
+
],
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
const hubLabel = s.table(
|
|
216
|
+
"DailyReportHub_Label",
|
|
217
|
+
{
|
|
218
|
+
hubId: bigint("hub_id", { mode: "number" }).notNull(),
|
|
219
|
+
labelId: bigint("label_id", { mode: "number" }).notNull(),
|
|
220
|
+
createdAt: datetime2("created_at").notNull(),
|
|
221
|
+
createdBy: nvarchar("created_by", { length: 50 }).notNull(),
|
|
222
|
+
},
|
|
223
|
+
(table) => [
|
|
224
|
+
primaryKey({ name: `${schemaName}_DailyReportHub_Label_pk`, columns: [table.hubId, table.labelId] }),
|
|
225
|
+
foreignKey({
|
|
226
|
+
name: `${schemaName}_DailyReportHub_Label_hub_id_fk`,
|
|
227
|
+
columns: [table.hubId],
|
|
228
|
+
foreignColumns: [hub.id],
|
|
229
|
+
}),
|
|
230
|
+
foreignKey({
|
|
231
|
+
name: `${schemaName}_DailyReportHub_Label_label_id_fk`,
|
|
232
|
+
columns: [table.labelId],
|
|
233
|
+
foreignColumns: [label.id],
|
|
234
|
+
}),
|
|
235
|
+
],
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
const userStatus = s.table(
|
|
239
|
+
"DailyReportUserStatus",
|
|
240
|
+
{
|
|
241
|
+
hubId: bigint("hub_id", { mode: "number" }).notNull(),
|
|
242
|
+
userId: bigint("user_id", { mode: "number" }).notNull(),
|
|
243
|
+
isRead: bit("is_read").default(false).notNull(),
|
|
244
|
+
isStarred: bit("is_starred").default(false).notNull(),
|
|
245
|
+
createdAt: datetime2("created_at").notNull(),
|
|
246
|
+
createdBy: nvarchar("created_by", { length: 50 }).notNull(),
|
|
247
|
+
updatedAt: datetime2("updated_at").notNull(),
|
|
248
|
+
updatedBy: nvarchar("updated_by", { length: 50 }).notNull(),
|
|
249
|
+
},
|
|
250
|
+
(table) => [
|
|
251
|
+
primaryKey({ name: `${schemaName}_DailyReportUserStatus_pk`, columns: [table.hubId, table.userId] }),
|
|
252
|
+
foreignKey({
|
|
253
|
+
name: `${schemaName}_DailyReportUserStatus_hub_id_fk`,
|
|
254
|
+
columns: [table.hubId],
|
|
255
|
+
foreignColumns: [hub.id],
|
|
256
|
+
}),
|
|
257
|
+
foreignKey({
|
|
258
|
+
name: `${schemaName}_DailyReportUserStatus_user_id_fk`,
|
|
259
|
+
columns: [table.userId],
|
|
260
|
+
foreignColumns: [users.id],
|
|
261
|
+
}),
|
|
262
|
+
],
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
return { hub, internal, comment, label, hubLabel, userStatus } satisfies DailyReportTables
|
|
266
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { bigint, mssqlSchema, nvarchar, primaryKey } from "drizzle-orm/mssql-core"
|
|
2
|
+
import { describe, expect, it } from "vitest"
|
|
3
|
+
import { createEpochStore, SqlResultCache } from "./cache"
|
|
4
|
+
import { defineDailyReportSchema } from "./schema"
|
|
5
|
+
import { createDailyReportService, type DailyReportDb, type DailyReportHubRow } from "./service"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Security regression tests for audit-actor masking in mapHubRecord.
|
|
9
|
+
* mapHubRecord の監査アクターマスク (生内部 ID 漏洩防止) のセキュリティ回帰テスト。
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// 実テーブル (getColumns / eq / desc が実カラムを要求するため mock 不可)
|
|
13
|
+
const testUsers = mssqlSchema("test").table("Users", { id: bigint("id", { mode: "number" }).notNull(), displayName: nvarchar("display_name", { length: 100 }) }, (t) => [primaryKey({ columns: [t.id] })])
|
|
14
|
+
const tables = defineDailyReportSchema("test", { userTable: testUsers })
|
|
15
|
+
|
|
16
|
+
/** getDailyReportsByBusinessDate 経路 (select→from→leftJoin→leftJoin→where→orderBy) だけを満たす最小モック。 */
|
|
17
|
+
const makeDb = (rows: unknown[]): DailyReportDb => {
|
|
18
|
+
const chain = {
|
|
19
|
+
from: () => chain,
|
|
20
|
+
innerJoin: () => chain,
|
|
21
|
+
leftJoin: () => chain,
|
|
22
|
+
where: () => chain,
|
|
23
|
+
top: () => chain,
|
|
24
|
+
orderBy: () => Promise.resolve(rows),
|
|
25
|
+
}
|
|
26
|
+
return { select: () => chain } as unknown as DailyReportDb
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const baseHub = (over: Partial<DailyReportHubRow>): DailyReportHubRow => ({
|
|
30
|
+
id: 1,
|
|
31
|
+
sourceType: "Internal",
|
|
32
|
+
sourceId: "1",
|
|
33
|
+
sourceIdNum: 1,
|
|
34
|
+
businessDate: "2099-01-05",
|
|
35
|
+
userId: 8,
|
|
36
|
+
title: "t",
|
|
37
|
+
summary: "s",
|
|
38
|
+
createdAt: "2099-01-05 00:00:00",
|
|
39
|
+
createdBy: "8",
|
|
40
|
+
updatedAt: "2099-01-05 00:00:00",
|
|
41
|
+
updatedBy: "8",
|
|
42
|
+
deletedAt: null,
|
|
43
|
+
deletedBy: null,
|
|
44
|
+
...over,
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const makeService = (rows: unknown[]) => {
|
|
48
|
+
const epochs = createEpochStore(undefined)
|
|
49
|
+
const cache = new SqlResultCache({ defaultTtlMs: 60_000 }, epochs)
|
|
50
|
+
return createDailyReportService({
|
|
51
|
+
db: makeDb(rows),
|
|
52
|
+
tables,
|
|
53
|
+
userTable: testUsers,
|
|
54
|
+
resolveUserId: async () => 8,
|
|
55
|
+
encodeUserId: (id) => `ENC${id}`, // テスト用の識別可能な難読化
|
|
56
|
+
cache,
|
|
57
|
+
epochs,
|
|
58
|
+
draftLabelName: "draft", // 必須注入 (ドラフト可視性フィルタ用のラベル名。テストは中立値で十分)
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
describe("mapHubRecord audit-actor masking (security)", () => {
|
|
63
|
+
it("数値の updatedBy / createdBy (= 生内部 ID) は encodeUserId で難読化される", async () => {
|
|
64
|
+
const svc = makeService([{ hub: baseHub({ createdBy: "8", updatedBy: "8" }), internal: null, creatorName: null }])
|
|
65
|
+
const [r] = await svc.getDailyReportsByBusinessDate("2099-01-05", { forceRefresh: true })
|
|
66
|
+
// 生 ID "8" がそのまま出ないこと (漏洩防止の核心)
|
|
67
|
+
expect(r.updatedBy).toBe("ENC8")
|
|
68
|
+
expect(r.updatedBy).not.toBe("8")
|
|
69
|
+
expect(r.author).toBe("ENC8") // creatorName(null) → createdBy をマスク
|
|
70
|
+
expect(r.employeeName).toBe("ENC8")
|
|
71
|
+
// userId フィールドは従来どおり難読化
|
|
72
|
+
expect(r.userId).toBe("ENC8")
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it("表示名 (creatorName) がある場合はマスクせず表示名を優先する", async () => {
|
|
76
|
+
const svc = makeService([{ hub: baseHub({ createdBy: "8", updatedBy: "8" }), internal: null, creatorName: "山田太郎" }])
|
|
77
|
+
const [r] = await svc.getDailyReportsByBusinessDate("2099-01-05", { forceRefresh: true })
|
|
78
|
+
expect(r.author).toBe("山田太郎")
|
|
79
|
+
expect(r.employeeName).toBe("山田太郎")
|
|
80
|
+
// updatedBy は表示名解決を持たないため常にマスク
|
|
81
|
+
expect(r.updatedBy).toBe("ENC8")
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it("非数値の監査値 (レガシー氏名/システム文字列) はそのまま通す", async () => {
|
|
85
|
+
const svc = makeService([{ hub: baseHub({ createdBy: "system-import", updatedBy: "batch" }), internal: null, creatorName: null }])
|
|
86
|
+
const [r] = await svc.getDailyReportsByBusinessDate("2099-01-05", { forceRefresh: true })
|
|
87
|
+
expect(r.author).toBe("system-import")
|
|
88
|
+
expect(r.updatedBy).toBe("batch")
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it("空 / null の監査値は null になる (生値の漏れなし)", async () => {
|
|
92
|
+
const svc = makeService([{ hub: baseHub({ createdBy: "", updatedBy: "" }), internal: null, creatorName: null }])
|
|
93
|
+
const [r] = await svc.getDailyReportsByBusinessDate("2099-01-05", { forceRefresh: true })
|
|
94
|
+
expect(r.updatedBy).toBeNull()
|
|
95
|
+
expect(r.author).toBe("") // author は非 null 契約のため空文字
|
|
96
|
+
})
|
|
97
|
+
})
|