@rebasepro/server-postgresql 0.5.0 → 0.6.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/dist/{server-postgresql/src/PostgresAdapter.d.ts → PostgresAdapter.d.ts} +1 -1
- package/dist/{server-postgresql/src/PostgresBackendDriver.d.ts → PostgresBackendDriver.d.ts} +2 -2
- package/dist/{server-postgresql/src/PostgresBootstrapper.d.ts → PostgresBootstrapper.d.ts} +11 -1
- package/dist/{server-postgresql/src/collections → collections}/PostgresCollectionRegistry.d.ts +4 -0
- package/dist/index.es.js +10207 -11145
- package/dist/index.es.js.map +1 -1
- package/dist/{server-postgresql/src/services → services}/EntityPersistService.d.ts +0 -14
- package/dist/utils/pg-array-null-patch.d.ts +16 -0
- package/dist/utils/pg-error-utils.d.ts +55 -0
- package/package.json +24 -21
- package/src/PostgresAdapter.ts +9 -10
- package/src/PostgresBackendDriver.ts +134 -121
- package/src/PostgresBootstrapper.ts +100 -30
- package/src/auth/ensure-tables.ts +28 -5
- package/src/auth/services.ts +28 -18
- package/src/cli.ts +99 -96
- package/src/collections/PostgresCollectionRegistry.ts +7 -0
- package/src/connection.ts +11 -6
- package/src/data-transformer.ts +25 -15
- package/src/databasePoolManager.ts +3 -2
- package/src/history/HistoryService.ts +3 -2
- package/src/history/ensure-history-table.ts +5 -4
- package/src/schema/auth-schema.ts +1 -2
- package/src/schema/doctor-cli.ts +2 -1
- package/src/schema/doctor.ts +40 -37
- package/src/schema/generate-drizzle-schema-logic.ts +56 -18
- package/src/schema/generate-drizzle-schema.ts +11 -11
- package/src/schema/introspect-db-inference.ts +25 -25
- package/src/schema/introspect-db-logic.ts +38 -38
- package/src/schema/introspect-db.ts +28 -27
- package/src/services/BranchService.ts +14 -0
- package/src/services/EntityFetchService.ts +28 -25
- package/src/services/EntityPersistService.ts +19 -149
- package/src/services/RelationService.ts +57 -37
- package/src/services/entity-helpers.ts +6 -2
- package/src/services/realtimeService.ts +45 -32
- package/src/utils/drizzle-conditions.ts +31 -15
- package/src/utils/pg-array-null-patch.ts +42 -0
- package/src/utils/pg-error-utils.ts +211 -0
- package/src/websocket.ts +15 -12
- package/test/array-null-safety.test.ts +335 -0
- package/test/auth-services.test.ts +36 -19
- package/test/batch-many-to-many-regression.test.ts +119 -39
- package/test/data-transformer-hardening.test.ts +67 -33
- package/test/data-transformer.test.ts +57 -4
- package/test/doctor.test.ts +10 -5
- package/test/drizzle-conditions.test.ts +59 -6
- package/test/generate-drizzle-schema.test.ts +65 -40
- package/test/introspect-db-generation.test.ts +179 -81
- package/test/introspect-db-utils.test.ts +92 -37
- package/test/mocks/chalk.cjs +7 -0
- package/test/pg-array-null-patch.test.ts +65 -0
- package/test/pg-error-utils.test.ts +221 -0
- package/test/postgresDataDriver.test.ts +14 -5
- package/test/property-ordering.test.ts +126 -79
- package/test/realtimeService.test.ts +6 -2
- package/test/relation-pipeline-gaps.test.ts +84 -36
- package/test/relations.test.ts +247 -0
- package/test/unmapped-tables-safety.test.ts +14 -6
- package/test/websocket.test.ts +1 -1
- package/tsconfig.json +5 -0
- package/tsconfig.prod.json +3 -0
- package/vite.config.ts +12 -10
- package/dist/common/src/collections/CollectionRegistry.d.ts +0 -56
- package/dist/common/src/collections/default-collections.d.ts +0 -9
- package/dist/common/src/collections/index.d.ts +0 -2
- package/dist/common/src/data/buildRebaseData.d.ts +0 -14
- package/dist/common/src/data/query_builder.d.ts +0 -55
- package/dist/common/src/index.d.ts +0 -4
- package/dist/common/src/util/builders.d.ts +0 -57
- package/dist/common/src/util/callbacks.d.ts +0 -6
- package/dist/common/src/util/collections.d.ts +0 -11
- package/dist/common/src/util/common.d.ts +0 -2
- package/dist/common/src/util/conditions.d.ts +0 -26
- package/dist/common/src/util/entities.d.ts +0 -58
- package/dist/common/src/util/enums.d.ts +0 -3
- package/dist/common/src/util/index.d.ts +0 -16
- package/dist/common/src/util/navigation_from_path.d.ts +0 -34
- package/dist/common/src/util/navigation_utils.d.ts +0 -20
- package/dist/common/src/util/parent_references_from_path.d.ts +0 -6
- package/dist/common/src/util/paths.d.ts +0 -14
- package/dist/common/src/util/permissions.d.ts +0 -14
- package/dist/common/src/util/references.d.ts +0 -2
- package/dist/common/src/util/relations.d.ts +0 -22
- package/dist/common/src/util/resolutions.d.ts +0 -72
- package/dist/common/src/util/storage.d.ts +0 -24
- package/dist/index.umd.js +0 -11749
- package/dist/index.umd.js.map +0 -1
- package/dist/types/src/controllers/analytics_controller.d.ts +0 -7
- package/dist/types/src/controllers/auth.d.ts +0 -104
- package/dist/types/src/controllers/client.d.ts +0 -168
- package/dist/types/src/controllers/collection_registry.d.ts +0 -46
- package/dist/types/src/controllers/customization_controller.d.ts +0 -60
- package/dist/types/src/controllers/data.d.ts +0 -207
- package/dist/types/src/controllers/data_driver.d.ts +0 -218
- package/dist/types/src/controllers/database_admin.d.ts +0 -11
- package/dist/types/src/controllers/dialogs_controller.d.ts +0 -36
- package/dist/types/src/controllers/effective_role.d.ts +0 -4
- package/dist/types/src/controllers/email.d.ts +0 -36
- package/dist/types/src/controllers/index.d.ts +0 -18
- package/dist/types/src/controllers/local_config_persistence.d.ts +0 -20
- package/dist/types/src/controllers/navigation.d.ts +0 -225
- package/dist/types/src/controllers/registry.d.ts +0 -63
- package/dist/types/src/controllers/side_dialogs_controller.d.ts +0 -67
- package/dist/types/src/controllers/side_entity_controller.d.ts +0 -97
- package/dist/types/src/controllers/snackbar.d.ts +0 -24
- package/dist/types/src/controllers/storage.d.ts +0 -171
- package/dist/types/src/index.d.ts +0 -4
- package/dist/types/src/rebase_context.d.ts +0 -122
- package/dist/types/src/types/auth_adapter.d.ts +0 -301
- package/dist/types/src/types/backend.d.ts +0 -571
- package/dist/types/src/types/backend_hooks.d.ts +0 -172
- package/dist/types/src/types/builders.d.ts +0 -15
- package/dist/types/src/types/chips.d.ts +0 -5
- package/dist/types/src/types/collections.d.ts +0 -961
- package/dist/types/src/types/component_ref.d.ts +0 -47
- package/dist/types/src/types/cron.d.ts +0 -102
- package/dist/types/src/types/data_source.d.ts +0 -64
- package/dist/types/src/types/database_adapter.d.ts +0 -94
- package/dist/types/src/types/entities.d.ts +0 -145
- package/dist/types/src/types/entity_actions.d.ts +0 -104
- package/dist/types/src/types/entity_callbacks.d.ts +0 -173
- package/dist/types/src/types/entity_link_builder.d.ts +0 -7
- package/dist/types/src/types/entity_overrides.d.ts +0 -10
- package/dist/types/src/types/entity_views.d.ts +0 -87
- package/dist/types/src/types/export_import.d.ts +0 -21
- package/dist/types/src/types/formex.d.ts +0 -40
- package/dist/types/src/types/index.d.ts +0 -28
- package/dist/types/src/types/locales.d.ts +0 -4
- package/dist/types/src/types/modify_collections.d.ts +0 -5
- package/dist/types/src/types/plugins.d.ts +0 -282
- package/dist/types/src/types/properties.d.ts +0 -1173
- package/dist/types/src/types/property_config.d.ts +0 -74
- package/dist/types/src/types/relations.d.ts +0 -336
- package/dist/types/src/types/slots.d.ts +0 -262
- package/dist/types/src/types/translations.d.ts +0 -900
- package/dist/types/src/types/user_management_delegate.d.ts +0 -86
- package/dist/types/src/types/websockets.d.ts +0 -78
- package/dist/types/src/users/index.d.ts +0 -1
- package/dist/types/src/users/user.d.ts +0 -50
- /package/dist/{server-postgresql/src/auth → auth}/ensure-tables.d.ts +0 -0
- /package/dist/{server-postgresql/src/auth → auth}/services.d.ts +0 -0
- /package/dist/{server-postgresql/src/cli.d.ts → cli.d.ts} +0 -0
- /package/dist/{server-postgresql/src/connection.d.ts → connection.d.ts} +0 -0
- /package/dist/{server-postgresql/src/data-transformer.d.ts → data-transformer.d.ts} +0 -0
- /package/dist/{server-postgresql/src/databasePoolManager.d.ts → databasePoolManager.d.ts} +0 -0
- /package/dist/{server-postgresql/src/history → history}/HistoryService.d.ts +0 -0
- /package/dist/{server-postgresql/src/history → history}/ensure-history-table.d.ts +0 -0
- /package/dist/{server-postgresql/src/index.d.ts → index.d.ts} +0 -0
- /package/dist/{server-postgresql/src/interfaces.d.ts → interfaces.d.ts} +0 -0
- /package/dist/{server-postgresql/src/schema → schema}/auth-schema.d.ts +0 -0
- /package/dist/{server-postgresql/src/schema → schema}/doctor-cli.d.ts +0 -0
- /package/dist/{server-postgresql/src/schema → schema}/doctor.d.ts +0 -0
- /package/dist/{server-postgresql/src/schema → schema}/generate-drizzle-schema-logic.d.ts +0 -0
- /package/dist/{server-postgresql/src/schema → schema}/generate-drizzle-schema.d.ts +0 -0
- /package/dist/{server-postgresql/src/schema → schema}/introspect-db-inference.d.ts +0 -0
- /package/dist/{server-postgresql/src/schema → schema}/introspect-db-logic.d.ts +0 -0
- /package/dist/{server-postgresql/src/schema → schema}/introspect-db.d.ts +0 -0
- /package/dist/{server-postgresql/src/schema → schema}/test-schema.d.ts +0 -0
- /package/dist/{server-postgresql/src/services → services}/BranchService.d.ts +0 -0
- /package/dist/{server-postgresql/src/services → services}/EntityFetchService.d.ts +0 -0
- /package/dist/{server-postgresql/src/services → services}/RelationService.d.ts +0 -0
- /package/dist/{server-postgresql/src/services → services}/entity-helpers.d.ts +0 -0
- /package/dist/{server-postgresql/src/services → services}/entityService.d.ts +0 -0
- /package/dist/{server-postgresql/src/services → services}/index.d.ts +0 -0
- /package/dist/{server-postgresql/src/services → services}/realtimeService.d.ts +0 -0
- /package/dist/{server-postgresql/src/types.d.ts → types.d.ts} +0 -0
- /package/dist/{server-postgresql/src/utils → utils}/drizzle-conditions.d.ts +0 -0
- /package/dist/{server-postgresql/src/websocket.d.ts → websocket.d.ts} +0 -0
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import { describe, it, expect } from "@jest/globals";
|
|
2
|
+
import {
|
|
3
|
+
serializePropertyToServer,
|
|
4
|
+
parsePropertyFromServer,
|
|
5
|
+
serializeDataToServer
|
|
6
|
+
} from "../src/data-transformer";
|
|
7
|
+
import type { Property, Properties, EntityCollection } from "@rebasepro/types";
|
|
8
|
+
|
|
9
|
+
// ─────────────────────────────────────────────────────────────
|
|
10
|
+
// Fixture helpers
|
|
11
|
+
// ─────────────────────────────────────────────────────────────
|
|
12
|
+
function makeCollection(slug: string, properties: Properties): EntityCollection {
|
|
13
|
+
return {
|
|
14
|
+
name: slug,
|
|
15
|
+
slug,
|
|
16
|
+
path: slug,
|
|
17
|
+
collectionType: "postgres",
|
|
18
|
+
tableName: slug,
|
|
19
|
+
properties
|
|
20
|
+
} as unknown as EntityCollection;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ─────────────────────────────────────────────────────────────
|
|
24
|
+
// Property fixtures
|
|
25
|
+
// ─────────────────────────────────────────────────────────────
|
|
26
|
+
const arrayOfStringsProp: Property = {
|
|
27
|
+
type: "array",
|
|
28
|
+
name: "Tags",
|
|
29
|
+
of: { type: "string", name: "Tag" }
|
|
30
|
+
} as Property;
|
|
31
|
+
|
|
32
|
+
const arrayOfNumbersProp: Property = {
|
|
33
|
+
type: "array",
|
|
34
|
+
name: "Scores",
|
|
35
|
+
of: { type: "number", name: "Score" }
|
|
36
|
+
} as Property;
|
|
37
|
+
|
|
38
|
+
const arrayOfMapsProp: Property = {
|
|
39
|
+
type: "array",
|
|
40
|
+
name: "Addresses",
|
|
41
|
+
of: {
|
|
42
|
+
type: "map",
|
|
43
|
+
name: "Address",
|
|
44
|
+
properties: {
|
|
45
|
+
street: { type: "string", name: "Street" },
|
|
46
|
+
city: { type: "string", name: "City" }
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
} as Property;
|
|
50
|
+
|
|
51
|
+
const arrayOneOfProp: Property = {
|
|
52
|
+
type: "array",
|
|
53
|
+
name: "Blocks",
|
|
54
|
+
oneOf: {
|
|
55
|
+
typeField: "type",
|
|
56
|
+
valueField: "value",
|
|
57
|
+
properties: {
|
|
58
|
+
text: { type: "string", name: "Text" } as Property,
|
|
59
|
+
number: { type: "number", name: "Number" } as Property
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
} as Property;
|
|
63
|
+
|
|
64
|
+
const arrayOfRelationsProp: Property = {
|
|
65
|
+
type: "array",
|
|
66
|
+
name: "Authors",
|
|
67
|
+
of: { type: "relation", name: "Author" }
|
|
68
|
+
} as Property;
|
|
69
|
+
|
|
70
|
+
// A bare array property without `of` or `oneOf`
|
|
71
|
+
const bareArrayProp: Property = {
|
|
72
|
+
type: "array",
|
|
73
|
+
name: "RawArray"
|
|
74
|
+
} as Property;
|
|
75
|
+
|
|
76
|
+
// ─────────────────────────────────────────────────────────────
|
|
77
|
+
// 1. serializePropertyToServer — array null safety
|
|
78
|
+
// ─────────────────────────────────────────────────────────────
|
|
79
|
+
describe("serializePropertyToServer — array null safety", () => {
|
|
80
|
+
|
|
81
|
+
// ── Null / undefined early return ──
|
|
82
|
+
it("returns null when value is null with array-of-strings property", () => {
|
|
83
|
+
expect(serializePropertyToServer(null, arrayOfStringsProp)).toBeNull();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("returns undefined when value is undefined with array-of-strings property", () => {
|
|
87
|
+
expect(serializePropertyToServer(undefined, arrayOfStringsProp)).toBeUndefined();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// ── Empty array ──
|
|
91
|
+
it("returns [] when value is an empty array", () => {
|
|
92
|
+
expect(serializePropertyToServer([], arrayOfStringsProp)).toEqual([]);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// ── Null with different sub-types ──
|
|
96
|
+
it("returns null when value is null with array-of-numbers property", () => {
|
|
97
|
+
expect(serializePropertyToServer(null, arrayOfNumbersProp)).toBeNull();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("returns null when value is null with array-of-maps property", () => {
|
|
101
|
+
expect(serializePropertyToServer(null, arrayOfMapsProp)).toBeNull();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("returns null when value is null with array oneOf property", () => {
|
|
105
|
+
expect(serializePropertyToServer(null, arrayOneOfProp)).toBeNull();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// ── Non-array values (coerced to empty array) ──
|
|
109
|
+
it("coerces a non-array string to empty array for array property", () => {
|
|
110
|
+
expect(serializePropertyToServer("not-an-array", arrayOfStringsProp)).toEqual([]);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("coerces a non-array number to empty array for array property", () => {
|
|
114
|
+
expect(serializePropertyToServer(42, arrayOfStringsProp)).toEqual([]);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("coerces a non-array object to empty array for bare array property", () => {
|
|
118
|
+
const obj = { a: 1 };
|
|
119
|
+
expect(serializePropertyToServer(obj, bareArrayProp)).toEqual([]);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("coerces a boolean to empty array for array property", () => {
|
|
123
|
+
expect(serializePropertyToServer(true, arrayOfStringsProp)).toEqual([]);
|
|
124
|
+
expect(serializePropertyToServer(false, arrayOfStringsProp)).toEqual([]);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// ── Arrays with null / undefined elements ──
|
|
128
|
+
it("handles array with null elements by passing each through sub-serialization", () => {
|
|
129
|
+
const result = serializePropertyToServer([null, "a", null], arrayOfStringsProp);
|
|
130
|
+
expect(result).toEqual([null, "a", null]);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("handles array with undefined elements by passing each through sub-serialization", () => {
|
|
134
|
+
const result = serializePropertyToServer([undefined, "a"], arrayOfStringsProp);
|
|
135
|
+
expect(result).toEqual([undefined, "a"]);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// ── Array of relation objects with null entries ──
|
|
139
|
+
it("serializes array of relation objects with null entries correctly", () => {
|
|
140
|
+
const value = [{ id: "1" }, null, { id: "2" }];
|
|
141
|
+
const result = serializePropertyToServer(value, arrayOfRelationsProp);
|
|
142
|
+
// null entries pass through serializePropertyToServer for the sub-property
|
|
143
|
+
// which returns null for null values
|
|
144
|
+
expect(result).toEqual(["1", null, "2"]);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// ── Array of maps where some entries are null ──
|
|
148
|
+
it("handles array of maps where some entries are null gracefully", () => {
|
|
149
|
+
const value = [
|
|
150
|
+
{ street: "123 Main", city: "NYC" },
|
|
151
|
+
null,
|
|
152
|
+
{ street: "456 Oak", city: "LA" }
|
|
153
|
+
];
|
|
154
|
+
const result = serializePropertyToServer(value, arrayOfMapsProp);
|
|
155
|
+
expect(result).toEqual([
|
|
156
|
+
{ street: "123 Main", city: "NYC" },
|
|
157
|
+
null,
|
|
158
|
+
{ street: "456 Oak", city: "LA" }
|
|
159
|
+
]);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// ── Empty array with oneOf property ──
|
|
163
|
+
it("returns [] when value is an empty array with oneOf property", () => {
|
|
164
|
+
expect(serializePropertyToServer([], arrayOneOfProp)).toEqual([]);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// ── Nested array of arrays (unusual) ──
|
|
168
|
+
it("returns nested array of arrays as-is for bare array property", () => {
|
|
169
|
+
const nested = [["a", "b"], ["c"]];
|
|
170
|
+
// Without `of` or `oneOf`, the array case falls through to `return value`
|
|
171
|
+
expect(serializePropertyToServer(nested, bareArrayProp)).toEqual([["a", "b"], ["c"]]);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// ─────────────────────────────────────────────────────────────
|
|
176
|
+
// 2. parsePropertyFromServer — array null safety
|
|
177
|
+
// ─────────────────────────────────────────────────────────────
|
|
178
|
+
describe("parsePropertyFromServer — array null safety", () => {
|
|
179
|
+
const dummyCollection = makeCollection("items", {
|
|
180
|
+
tags: arrayOfStringsProp
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// ── Null / undefined early return ──
|
|
184
|
+
it("returns null when value is null with array property", () => {
|
|
185
|
+
expect(parsePropertyFromServer(null, arrayOfStringsProp, dummyCollection)).toBeNull();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("returns undefined when value is undefined with array property", () => {
|
|
189
|
+
expect(parsePropertyFromServer(undefined, arrayOfStringsProp, dummyCollection)).toBeUndefined();
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// ── Empty array ──
|
|
193
|
+
it("returns [] when value is an empty array with array-of-strings", () => {
|
|
194
|
+
expect(parsePropertyFromServer([], arrayOfStringsProp, dummyCollection)).toEqual([]);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// ── Non-array values (coerced to array) ──
|
|
198
|
+
it("coerces a non-array string to single-element array for array property", () => {
|
|
199
|
+
expect(parsePropertyFromServer("not-an-array", arrayOfStringsProp, dummyCollection)).toEqual(["not-an-array"]);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("coerces a non-array number to empty array for array property", () => {
|
|
203
|
+
expect(parsePropertyFromServer(99, arrayOfStringsProp, dummyCollection)).toEqual([]);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// ── Null with oneOf ──
|
|
207
|
+
it("returns null when value is null with array oneOf property", () => {
|
|
208
|
+
expect(parsePropertyFromServer(null, arrayOneOfProp, dummyCollection)).toBeNull();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// ── Array with null elements ──
|
|
212
|
+
it("handles array with null elements for parsing gracefully", () => {
|
|
213
|
+
const result = parsePropertyFromServer([null, "a", null], arrayOfStringsProp, dummyCollection);
|
|
214
|
+
expect(result).toEqual([null, "a", null]);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// ── Falsy non-null values (coerced to array) ──
|
|
218
|
+
it("coerces falsy number 0 to empty array for array property", () => {
|
|
219
|
+
expect(parsePropertyFromServer(0, arrayOfStringsProp, dummyCollection)).toEqual([]);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("coerces false to empty array for array property", () => {
|
|
223
|
+
expect(parsePropertyFromServer(false, arrayOfStringsProp, dummyCollection)).toEqual([]);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it("coerces empty string to single-element array for array property", () => {
|
|
227
|
+
expect(parsePropertyFromServer("", arrayOfStringsProp, dummyCollection)).toEqual([""]);
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// ─────────────────────────────────────────────────────────────
|
|
232
|
+
// 3. serializeDataToServer — entity with null array fields
|
|
233
|
+
// ─────────────────────────────────────────────────────────────
|
|
234
|
+
describe("serializeDataToServer — entity with null array fields", () => {
|
|
235
|
+
const tagsProperty: Property = {
|
|
236
|
+
type: "array",
|
|
237
|
+
name: "Tags",
|
|
238
|
+
of: { type: "string", name: "Tag" }
|
|
239
|
+
} as Property;
|
|
240
|
+
|
|
241
|
+
const categoriesProperty: Property = {
|
|
242
|
+
type: "array",
|
|
243
|
+
name: "Categories",
|
|
244
|
+
of: { type: "string", name: "Category" }
|
|
245
|
+
} as Property;
|
|
246
|
+
|
|
247
|
+
const metadataProperty: Property = {
|
|
248
|
+
type: "map",
|
|
249
|
+
name: "Metadata",
|
|
250
|
+
properties: {
|
|
251
|
+
labels: {
|
|
252
|
+
type: "array",
|
|
253
|
+
name: "Labels",
|
|
254
|
+
of: { type: "string", name: "Label" }
|
|
255
|
+
} as Property
|
|
256
|
+
}
|
|
257
|
+
} as Property;
|
|
258
|
+
|
|
259
|
+
const properties: Properties = {
|
|
260
|
+
tags: tagsProperty
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
// ── Null array field ──
|
|
264
|
+
it("serializes entity with tags: null — scalarData.tags should be null", () => {
|
|
265
|
+
const result = serializeDataToServer(
|
|
266
|
+
{ tags: null },
|
|
267
|
+
properties
|
|
268
|
+
);
|
|
269
|
+
expect(result.scalarData.tags).toBeNull();
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// ── Undefined array field ──
|
|
273
|
+
it("serializes entity with tags: undefined — tags value should be undefined in scalarData", () => {
|
|
274
|
+
const result = serializeDataToServer(
|
|
275
|
+
{ tags: undefined },
|
|
276
|
+
properties
|
|
277
|
+
);
|
|
278
|
+
// Object.entries iterates keys even when value is undefined,
|
|
279
|
+
// and serializePropertyToServer returns undefined for undefined input
|
|
280
|
+
expect(result.scalarData.tags).toBeUndefined();
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// ── Empty array field ──
|
|
284
|
+
it("serializes entity with tags: [] — scalarData.tags should be []", () => {
|
|
285
|
+
const result = serializeDataToServer(
|
|
286
|
+
{ tags: [] },
|
|
287
|
+
properties
|
|
288
|
+
);
|
|
289
|
+
expect(result.scalarData.tags).toEqual([]);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// ── Multiple array fields, mixed null / populated ──
|
|
293
|
+
it("handles entity with multiple array fields, some null, some populated", () => {
|
|
294
|
+
const multiProperties: Properties = {
|
|
295
|
+
tags: tagsProperty,
|
|
296
|
+
categories: categoriesProperty
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
const result = serializeDataToServer(
|
|
300
|
+
{ tags: null, categories: ["cat-a", "cat-b"] },
|
|
301
|
+
multiProperties
|
|
302
|
+
);
|
|
303
|
+
expect(result.scalarData.tags).toBeNull();
|
|
304
|
+
expect(result.scalarData.categories).toEqual(["cat-a", "cat-b"]);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// ── Nested map containing null array ──
|
|
308
|
+
it("handles entity with nested map containing array field that is null", () => {
|
|
309
|
+
const nestedProperties: Properties = {
|
|
310
|
+
metadata: metadataProperty
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
const result = serializeDataToServer(
|
|
314
|
+
{ metadata: { labels: null } },
|
|
315
|
+
nestedProperties
|
|
316
|
+
);
|
|
317
|
+
// The map serializer recurses into sub-properties; the inner array gets null-checked
|
|
318
|
+
expect(result.scalarData.metadata).toEqual({ labels: null });
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// ── All array fields null ──
|
|
322
|
+
it("handles entity with only array fields, all null — scalarData should have all null values", () => {
|
|
323
|
+
const allArrayProperties: Properties = {
|
|
324
|
+
tags: tagsProperty,
|
|
325
|
+
categories: categoriesProperty
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
const result = serializeDataToServer(
|
|
329
|
+
{ tags: null, categories: null },
|
|
330
|
+
allArrayProperties
|
|
331
|
+
);
|
|
332
|
+
expect(result.scalarData.tags).toBeNull();
|
|
333
|
+
expect(result.scalarData.categories).toBeNull();
|
|
334
|
+
});
|
|
335
|
+
});
|
|
@@ -8,7 +8,9 @@ jest.mock("drizzle-orm", () => {
|
|
|
8
8
|
const actual = jest.requireActual("drizzle-orm");
|
|
9
9
|
return {
|
|
10
10
|
...actual,
|
|
11
|
-
eq: jest.fn((field, value) => ({ field,
|
|
11
|
+
eq: jest.fn((field, value) => ({ field,
|
|
12
|
+
value,
|
|
13
|
+
type: "eq" })),
|
|
12
14
|
sql: Object.assign(
|
|
13
15
|
jest.fn((strings: TemplateStringsArray, ...values: unknown[]) => ({
|
|
14
16
|
strings,
|
|
@@ -16,8 +18,11 @@ jest.mock("drizzle-orm", () => {
|
|
|
16
18
|
type: "sql"
|
|
17
19
|
})),
|
|
18
20
|
{
|
|
19
|
-
raw: jest.fn((val: string) => ({ val,
|
|
20
|
-
|
|
21
|
+
raw: jest.fn((val: string) => ({ val,
|
|
22
|
+
type: "sql-raw" })),
|
|
23
|
+
join: jest.fn((parts: unknown[], separator: unknown) => ({ parts,
|
|
24
|
+
separator,
|
|
25
|
+
type: "sql-join" }))
|
|
21
26
|
}
|
|
22
27
|
),
|
|
23
28
|
relations: jest.fn(() => ({}))
|
|
@@ -133,11 +138,11 @@ describe("Auth Services", () => {
|
|
|
133
138
|
email: "test@example.com",
|
|
134
139
|
displayName: "Test User"
|
|
135
140
|
};
|
|
136
|
-
const dbReturnedUser = {
|
|
141
|
+
const dbReturnedUser = {
|
|
137
142
|
id: "user-123",
|
|
138
143
|
...newUser,
|
|
139
144
|
createdAt: new Date(),
|
|
140
|
-
updatedAt: new Date()
|
|
145
|
+
updatedAt: new Date()
|
|
141
146
|
};
|
|
142
147
|
mockInsertReturning.mockResolvedValueOnce([dbReturnedUser]);
|
|
143
148
|
|
|
@@ -154,7 +159,8 @@ describe("Auth Services", () => {
|
|
|
154
159
|
|
|
155
160
|
describe("getUserById", () => {
|
|
156
161
|
it("should return user when found", async () => {
|
|
157
|
-
const mockUser = { id: "user-123",
|
|
162
|
+
const mockUser = { id: "user-123",
|
|
163
|
+
email: "test@example.com" };
|
|
158
164
|
mockSelectWhere.mockResolvedValueOnce([mockUser]);
|
|
159
165
|
|
|
160
166
|
const result = await userService.getUserById("user-123");
|
|
@@ -174,7 +180,8 @@ describe("Auth Services", () => {
|
|
|
174
180
|
|
|
175
181
|
describe("getUserByEmail", () => {
|
|
176
182
|
it("should return user when found by email", async () => {
|
|
177
|
-
const mockUser = { id: "user-123",
|
|
183
|
+
const mockUser = { id: "user-123",
|
|
184
|
+
email: "test@example.com" };
|
|
178
185
|
mockSelectWhere.mockResolvedValueOnce([mockUser]);
|
|
179
186
|
|
|
180
187
|
const result = await userService.getUserByEmail("test@example.com");
|
|
@@ -194,13 +201,15 @@ describe("Auth Services", () => {
|
|
|
194
201
|
|
|
195
202
|
describe("getUserByIdentity", () => {
|
|
196
203
|
it("should fetch user by identity", async () => {
|
|
197
|
-
const mockUser = { id: "user-123",
|
|
204
|
+
const mockUser = { id: "user-123",
|
|
205
|
+
email: "test@example.com" };
|
|
198
206
|
mockSelectWhere.mockResolvedValueOnce([{ user: mockUser }]);
|
|
199
207
|
|
|
200
208
|
const result = await userService.getUserByIdentity("google", "google-abc");
|
|
201
209
|
|
|
202
210
|
expect(db.select).toHaveBeenCalled();
|
|
203
|
-
expect(result).toEqual(expect.objectContaining({ id: "user-123",
|
|
211
|
+
expect(result).toEqual(expect.objectContaining({ id: "user-123",
|
|
212
|
+
email: "test@example.com" }));
|
|
204
213
|
});
|
|
205
214
|
});
|
|
206
215
|
|
|
@@ -223,10 +232,10 @@ describe("Auth Services", () => {
|
|
|
223
232
|
|
|
224
233
|
describe("updateUser", () => {
|
|
225
234
|
it("should update user and return updated record", async () => {
|
|
226
|
-
const updatedUser = {
|
|
235
|
+
const updatedUser = {
|
|
227
236
|
id: "user-123",
|
|
228
237
|
email: "test@example.com",
|
|
229
|
-
displayName: "Updated Name"
|
|
238
|
+
displayName: "Updated Name"
|
|
230
239
|
};
|
|
231
240
|
mockUpdateReturning.mockResolvedValueOnce([updatedUser]);
|
|
232
241
|
|
|
@@ -261,8 +270,10 @@ describe("Auth Services", () => {
|
|
|
261
270
|
describe("listUsers", () => {
|
|
262
271
|
it("should return all users", async () => {
|
|
263
272
|
const mockUsers = [
|
|
264
|
-
{ id: "user-1",
|
|
265
|
-
|
|
273
|
+
{ id: "user-1",
|
|
274
|
+
email: "user1@example.com" },
|
|
275
|
+
{ id: "user-2",
|
|
276
|
+
email: "user2@example.com" }
|
|
266
277
|
];
|
|
267
278
|
mockSelectFrom.mockReturnValueOnce(Promise.resolve(mockUsers));
|
|
268
279
|
|
|
@@ -270,8 +281,10 @@ describe("Auth Services", () => {
|
|
|
270
281
|
|
|
271
282
|
expect(db.select).toHaveBeenCalled();
|
|
272
283
|
expect(result).toEqual([
|
|
273
|
-
mockUserData({ id: "user-1",
|
|
274
|
-
|
|
284
|
+
mockUserData({ id: "user-1",
|
|
285
|
+
email: "user1@example.com" }),
|
|
286
|
+
mockUserData({ id: "user-2",
|
|
287
|
+
email: "user2@example.com" })
|
|
275
288
|
]);
|
|
276
289
|
});
|
|
277
290
|
});
|
|
@@ -332,7 +345,8 @@ describe("Auth Services", () => {
|
|
|
332
345
|
|
|
333
346
|
describe("getUserByVerificationToken", () => {
|
|
334
347
|
it("should find user by verification token", async () => {
|
|
335
|
-
const mockUser = { id: "user-123",
|
|
348
|
+
const mockUser = { id: "user-123",
|
|
349
|
+
email: "test@example.com" };
|
|
336
350
|
mockSelectWhere.mockResolvedValueOnce([mockUser]);
|
|
337
351
|
|
|
338
352
|
const result = await userService.getUserByVerificationToken("token-abc");
|
|
@@ -396,7 +410,8 @@ describe("Auth Services", () => {
|
|
|
396
410
|
|
|
397
411
|
describe("getUserWithRoles", () => {
|
|
398
412
|
it("should return user with roles", async () => {
|
|
399
|
-
const mockUser = { id: "user-123",
|
|
413
|
+
const mockUser = { id: "user-123",
|
|
414
|
+
email: "test@example.com" };
|
|
400
415
|
mockSelectWhere.mockResolvedValueOnce([mockUser]);
|
|
401
416
|
mockExecute.mockResolvedValueOnce({
|
|
402
417
|
rows: [{ roles: ["admin"] }]
|
|
@@ -427,7 +442,8 @@ describe("Auth Services", () => {
|
|
|
427
442
|
it("should return paginated and filtered users list", async () => {
|
|
428
443
|
mockExecute
|
|
429
444
|
.mockResolvedValueOnce({ rows: [{ total: 1 }] })
|
|
430
|
-
.mockResolvedValueOnce({ rows: [{ id: "user-123",
|
|
445
|
+
.mockResolvedValueOnce({ rows: [{ id: "user-123",
|
|
446
|
+
email: "test@example.com" }] });
|
|
431
447
|
|
|
432
448
|
const result = await userService.listUsersPaginated({
|
|
433
449
|
limit: 10,
|
|
@@ -439,7 +455,8 @@ describe("Auth Services", () => {
|
|
|
439
455
|
|
|
440
456
|
expect(mockExecute).toHaveBeenCalledTimes(2);
|
|
441
457
|
expect(result).toEqual({
|
|
442
|
-
users: [mockUserData({ id: "user-123",
|
|
458
|
+
users: [mockUserData({ id: "user-123",
|
|
459
|
+
email: "test@example.com" })],
|
|
443
460
|
total: 1,
|
|
444
461
|
limit: 10,
|
|
445
462
|
offset: 0
|