dynamo-query-engine 1.0.5 → 1.0.7
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/package.json +1 -1
- package/src/core/expandResolver.js +119 -26
- package/tests/unit/expandResolver.test.js +356 -0
package/package.json
CHANGED
|
@@ -4,9 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import "../types/jsdoc.js";
|
|
6
6
|
import { modelRegistry } from "./modelRegistry.js";
|
|
7
|
-
import { buildGridQuery } from "./gridQueryBuilder.js";
|
|
8
|
-
import { validateExpandField } from "../utils/validation.js";
|
|
9
|
-
import { extractGridConfig } from "./gridQueryBuilder.js";
|
|
7
|
+
import { buildGridQuery, extractGridConfig } from "./gridQueryBuilder.js";
|
|
10
8
|
|
|
11
9
|
/**
|
|
12
10
|
* Extract expand policy from schema
|
|
@@ -62,6 +60,15 @@ export async function resolveExpand({
|
|
|
62
60
|
// Get target model from registry
|
|
63
61
|
const targetModel = modelRegistry.get(policy.type);
|
|
64
62
|
|
|
63
|
+
// Get the GraphQL type name from the schema (if defined)
|
|
64
|
+
const attributes = parentModel.schema.getAttributes();
|
|
65
|
+
const fieldConfig = attributes[field];
|
|
66
|
+
const graphqlTypeName = fieldConfig?.graphql?.type || policy.type;
|
|
67
|
+
|
|
68
|
+
// Determine which property name to use for storing the expanded data
|
|
69
|
+
// If graphql.type is defined, use it as the property name, otherwise use the field name
|
|
70
|
+
const expandPropertyName = fieldConfig?.graphql?.type || field;
|
|
71
|
+
|
|
65
72
|
// Determine limit based on args and policy
|
|
66
73
|
const requestedLimit = args.pagination?.pageSize ?? policy.defaultLimit;
|
|
67
74
|
const limit = Math.min(requestedLimit, policy.maxLimit);
|
|
@@ -69,41 +76,95 @@ export async function resolveExpand({
|
|
|
69
76
|
// Resolve relations for each parent item
|
|
70
77
|
const expandPromises = parentItems.map(async (parent) => {
|
|
71
78
|
try {
|
|
72
|
-
//
|
|
73
|
-
// This should be either parent.pk or
|
|
79
|
+
// Get partition key value from parent
|
|
80
|
+
// This should be either parent.pk or the first field (usually tenantId)
|
|
74
81
|
const partitionKeyValue = parent.pk || parent[Object.keys(parent)[0]];
|
|
75
82
|
|
|
76
83
|
if (!partitionKeyValue) {
|
|
77
84
|
console.warn(
|
|
78
85
|
`Cannot expand '${field}' for parent: missing partition key value`
|
|
79
86
|
);
|
|
80
|
-
parent[
|
|
87
|
+
parent[expandPropertyName] = policy.relation === "ONE" ? null : [];
|
|
81
88
|
return;
|
|
82
89
|
}
|
|
83
90
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
let results;
|
|
92
|
+
|
|
93
|
+
// For ONE relations with a foreignKey, we can optimize by directly querying for that specific item
|
|
94
|
+
if (policy.relation === "ONE" && policy.foreignKey) {
|
|
95
|
+
const foreignKeyValue = parent[policy.foreignKey];
|
|
96
|
+
|
|
97
|
+
if (!foreignKeyValue) {
|
|
98
|
+
console.warn(
|
|
99
|
+
`Cannot expand '${field}' for parent: missing foreign key value '${policy.foreignKey}'`
|
|
100
|
+
);
|
|
101
|
+
parent[expandPropertyName] = null;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Build a query that filters for the specific range key value
|
|
106
|
+
const targetAttributes = targetModel.schema.getAttributes();
|
|
107
|
+
const rangeKeyName = Object.keys(targetAttributes).find(
|
|
108
|
+
(key) => targetAttributes[key].rangeKey === true
|
|
109
|
+
);
|
|
93
110
|
|
|
94
|
-
|
|
95
|
-
|
|
111
|
+
if (!rangeKeyName) {
|
|
112
|
+
console.warn(
|
|
113
|
+
`Cannot expand '${field}': target model '${policy.type}' has no range key defined`
|
|
114
|
+
);
|
|
115
|
+
parent[expandPropertyName] = null;
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Build query with range key filter
|
|
120
|
+
const filterModel = JSON.stringify({
|
|
121
|
+
items: [
|
|
122
|
+
{
|
|
123
|
+
field: rangeKeyName,
|
|
124
|
+
operator: "equals",
|
|
125
|
+
value: foreignKeyValue,
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const { query } = buildGridQuery({
|
|
131
|
+
model: targetModel,
|
|
132
|
+
partitionKeyValue,
|
|
133
|
+
filterModel,
|
|
134
|
+
limit: 1,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
results = await query.exec();
|
|
138
|
+
} else {
|
|
139
|
+
// For MANY relations or ONE without foreignKey, use standard query
|
|
140
|
+
const { query } = buildGridQuery({
|
|
141
|
+
model: targetModel,
|
|
142
|
+
partitionKeyValue,
|
|
143
|
+
filterModel: args.filter,
|
|
144
|
+
sortModel: args.sort,
|
|
145
|
+
paginationModel: { pageSize: limit },
|
|
146
|
+
// No cursor for nested expands
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
results = await query.exec();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Add __typename to each result
|
|
153
|
+
const resultsWithTypename = results.map((item) => ({
|
|
154
|
+
...item,
|
|
155
|
+
__typename: graphqlTypeName,
|
|
156
|
+
}));
|
|
96
157
|
|
|
97
158
|
// Assign results to parent
|
|
98
159
|
if (policy.relation === "ONE") {
|
|
99
|
-
parent[
|
|
160
|
+
parent[expandPropertyName] = resultsWithTypename.length > 0 ? resultsWithTypename[0] : null;
|
|
100
161
|
} else {
|
|
101
|
-
parent[
|
|
162
|
+
parent[expandPropertyName] = resultsWithTypename;
|
|
102
163
|
}
|
|
103
164
|
} catch (error) {
|
|
104
165
|
console.error(`Error expanding '${field}' for parent:`, error);
|
|
105
166
|
// Gracefully handle errors - set empty result
|
|
106
|
-
parent[
|
|
167
|
+
parent[expandPropertyName] = policy.relation === "ONE" ? null : [];
|
|
107
168
|
}
|
|
108
169
|
});
|
|
109
170
|
|
|
@@ -111,6 +172,27 @@ export async function resolveExpand({
|
|
|
111
172
|
await Promise.all(expandPromises);
|
|
112
173
|
}
|
|
113
174
|
|
|
175
|
+
/**
|
|
176
|
+
* Create a mapping from GraphQL field names to schema field names
|
|
177
|
+
* @param {*} schema - Dynamoose schema
|
|
178
|
+
* @returns {Object.<string, string>} Map of GraphQL field name to schema field name
|
|
179
|
+
*/
|
|
180
|
+
function createGraphQLToSchemaFieldMap(schema) {
|
|
181
|
+
const attributes = schema.getAttributes();
|
|
182
|
+
const map = {};
|
|
183
|
+
|
|
184
|
+
for (const [fieldName, config] of Object.entries(attributes)) {
|
|
185
|
+
// If the field has a graphql.type config, map from graphql type to field name
|
|
186
|
+
if (config?.graphql?.type) {
|
|
187
|
+
map[config.graphql.type] = fieldName;
|
|
188
|
+
}
|
|
189
|
+
// Also keep the original field name mapping (for backward compatibility)
|
|
190
|
+
map[fieldName] = fieldName;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return map;
|
|
194
|
+
}
|
|
195
|
+
|
|
114
196
|
/**
|
|
115
197
|
* Resolve multiple expands from GraphQL resolve info
|
|
116
198
|
* @param {Array} parentItems - Parent items to expand
|
|
@@ -137,18 +219,29 @@ export async function resolveExpands(
|
|
|
137
219
|
|
|
138
220
|
// Get expand policy to filter only expandable fields
|
|
139
221
|
const expandPolicy = extractExpandPolicy(parentModel.schema);
|
|
222
|
+
|
|
223
|
+
// Create mapping from GraphQL field names to schema field names
|
|
224
|
+
const graphqlToSchemaMap = createGraphQLToSchemaFieldMap(parentModel.schema);
|
|
140
225
|
|
|
141
226
|
// Resolve all expands in parallel
|
|
142
227
|
const expandPromises = Object.entries(fields)
|
|
143
|
-
.
|
|
144
|
-
|
|
145
|
-
|
|
228
|
+
.map(([graphqlFieldName, fieldInfo]) => {
|
|
229
|
+
// Map GraphQL field name to schema field name
|
|
230
|
+
const schemaFieldName = graphqlToSchemaMap[graphqlFieldName];
|
|
231
|
+
|
|
232
|
+
// Check if this field is expandable
|
|
233
|
+
if (!schemaFieldName || !expandPolicy[schemaFieldName]) {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return resolveExpand({
|
|
146
238
|
parentItems,
|
|
147
239
|
parentModel,
|
|
148
|
-
field:
|
|
240
|
+
field: schemaFieldName,
|
|
149
241
|
args: fieldInfo.args || {},
|
|
150
|
-
})
|
|
151
|
-
)
|
|
242
|
+
});
|
|
243
|
+
})
|
|
244
|
+
.filter(Boolean); // Remove null entries
|
|
152
245
|
|
|
153
246
|
await Promise.all(expandPromises);
|
|
154
247
|
}
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tests for expand resolver with GraphQL type names
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
6
|
+
import { resolveExpand, resolveExpands } from "../../src/core/expandResolver.js";
|
|
7
|
+
import { modelRegistry } from "../../src/core/modelRegistry.js";
|
|
8
|
+
import { createMockSchema, createMockModel, createMockQuery } from "../mocks/dynamoose.mock.js";
|
|
9
|
+
|
|
10
|
+
describe("ExpandResolver", () => {
|
|
11
|
+
describe("resolveExpand with GraphQL type names", () => {
|
|
12
|
+
let parentModel;
|
|
13
|
+
let targetModel;
|
|
14
|
+
let parentItems;
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
// Clear registry
|
|
18
|
+
modelRegistry.clear();
|
|
19
|
+
|
|
20
|
+
// Create parent schema with unitId field that has graphql.type config
|
|
21
|
+
const parentSchema = createMockSchema({
|
|
22
|
+
tenantId: {
|
|
23
|
+
type: String,
|
|
24
|
+
hashKey: true,
|
|
25
|
+
},
|
|
26
|
+
reservationId: {
|
|
27
|
+
type: String,
|
|
28
|
+
rangeKey: true,
|
|
29
|
+
},
|
|
30
|
+
unitId: {
|
|
31
|
+
type: String,
|
|
32
|
+
query: {
|
|
33
|
+
expand: {
|
|
34
|
+
type: "Unit",
|
|
35
|
+
relation: "ONE",
|
|
36
|
+
foreignKey: "unitId", // Field in parent that references the child
|
|
37
|
+
defaultLimit: 1,
|
|
38
|
+
maxLimit: 1,
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
graphql: {
|
|
42
|
+
type: "Unit", // GraphQL type name
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Create target schema (Unit)
|
|
48
|
+
const targetSchema = createMockSchema({
|
|
49
|
+
tenantId: {
|
|
50
|
+
type: String,
|
|
51
|
+
hashKey: true,
|
|
52
|
+
},
|
|
53
|
+
unitId: {
|
|
54
|
+
type: String,
|
|
55
|
+
rangeKey: true,
|
|
56
|
+
},
|
|
57
|
+
name: {
|
|
58
|
+
type: String,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Create parent model
|
|
63
|
+
parentModel = createMockModel(parentSchema);
|
|
64
|
+
|
|
65
|
+
// Create target model with mock query result
|
|
66
|
+
const unitData = [
|
|
67
|
+
{
|
|
68
|
+
tenantId: "tenant-1",
|
|
69
|
+
unitId: "unit-1",
|
|
70
|
+
name: "Unit A",
|
|
71
|
+
},
|
|
72
|
+
];
|
|
73
|
+
targetModel = createMockModel(targetSchema, unitData);
|
|
74
|
+
|
|
75
|
+
// Register target model
|
|
76
|
+
modelRegistry.register("Unit", targetModel);
|
|
77
|
+
|
|
78
|
+
// Create parent items
|
|
79
|
+
parentItems = [
|
|
80
|
+
{
|
|
81
|
+
tenantId: "tenant-1",
|
|
82
|
+
reservationId: "res-1",
|
|
83
|
+
unitId: "unit-1",
|
|
84
|
+
},
|
|
85
|
+
];
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("should store expanded data under GraphQL type name (Unit) not field name (unitId)", async () => {
|
|
89
|
+
await resolveExpand({
|
|
90
|
+
parentItems,
|
|
91
|
+
parentModel,
|
|
92
|
+
field: "unitId",
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// The expanded data should be under 'Unit', not 'unitId'
|
|
96
|
+
expect(parentItems[0].Unit).toBeDefined();
|
|
97
|
+
expect(parentItems[0].Unit).toEqual({
|
|
98
|
+
tenantId: "tenant-1",
|
|
99
|
+
unitId: "unit-1",
|
|
100
|
+
name: "Unit A",
|
|
101
|
+
__typename: "Unit",
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("should add __typename field to expanded data", async () => {
|
|
106
|
+
await resolveExpand({
|
|
107
|
+
parentItems,
|
|
108
|
+
parentModel,
|
|
109
|
+
field: "unitId",
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
expect(parentItems[0].Unit.__typename).toBe("Unit");
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("should handle ONE relation correctly", async () => {
|
|
116
|
+
await resolveExpand({
|
|
117
|
+
parentItems,
|
|
118
|
+
parentModel,
|
|
119
|
+
field: "unitId",
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// ONE relation should return a single object, not an array
|
|
123
|
+
expect(parentItems[0].Unit).toBeDefined();
|
|
124
|
+
expect(Array.isArray(parentItems[0].Unit)).toBe(false);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("should handle MANY relation correctly", async () => {
|
|
128
|
+
// Clear and re-register with different config
|
|
129
|
+
modelRegistry.clear();
|
|
130
|
+
|
|
131
|
+
// Update parent schema for MANY relation
|
|
132
|
+
const parentSchemaMany = createMockSchema({
|
|
133
|
+
tenantId: {
|
|
134
|
+
type: String,
|
|
135
|
+
hashKey: true,
|
|
136
|
+
},
|
|
137
|
+
reservationId: {
|
|
138
|
+
type: String,
|
|
139
|
+
rangeKey: true,
|
|
140
|
+
},
|
|
141
|
+
unitId: {
|
|
142
|
+
type: String,
|
|
143
|
+
query: {
|
|
144
|
+
expand: {
|
|
145
|
+
type: "Unit",
|
|
146
|
+
relation: "MANY",
|
|
147
|
+
defaultLimit: 10,
|
|
148
|
+
maxLimit: 100,
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
graphql: {
|
|
152
|
+
type: "Units", // Plural for MANY relation
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const unitsData = [
|
|
158
|
+
{ tenantId: "tenant-1", unitId: "unit-1", name: "Unit A" },
|
|
159
|
+
{ tenantId: "tenant-1", unitId: "unit-2", name: "Unit B" },
|
|
160
|
+
];
|
|
161
|
+
|
|
162
|
+
const parentModelMany = createMockModel(parentSchemaMany);
|
|
163
|
+
const targetModelMany = createMockModel(targetModel.schema, unitsData);
|
|
164
|
+
|
|
165
|
+
modelRegistry.register("Unit", targetModelMany);
|
|
166
|
+
|
|
167
|
+
await resolveExpand({
|
|
168
|
+
parentItems,
|
|
169
|
+
parentModel: parentModelMany,
|
|
170
|
+
field: "unitId",
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// MANY relation should return an array
|
|
174
|
+
expect(parentItems[0].Units).toBeDefined();
|
|
175
|
+
expect(Array.isArray(parentItems[0].Units)).toBe(true);
|
|
176
|
+
expect(parentItems[0].Units).toHaveLength(2);
|
|
177
|
+
expect(parentItems[0].Units[0].__typename).toBe("Units");
|
|
178
|
+
expect(parentItems[0].Units[1].__typename).toBe("Units");
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("should use foreignKey to filter for specific item in ONE relation", async () => {
|
|
182
|
+
await resolveExpand({
|
|
183
|
+
parentItems,
|
|
184
|
+
parentModel,
|
|
185
|
+
field: "unitId",
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Should have expanded exactly one unit
|
|
189
|
+
expect(parentItems[0].Unit).toBeDefined();
|
|
190
|
+
expect(parentItems[0].Unit.unitId).toBe("unit-1");
|
|
191
|
+
expect(parentItems[0].Unit.name).toBe("Unit A");
|
|
192
|
+
|
|
193
|
+
// Verify that buildGridQuery was called with a filterModel
|
|
194
|
+
// (This would require spy/mock inspection in a more detailed test)
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("should fallback to field name when graphql.type is not defined", async () => {
|
|
198
|
+
// Clear and re-register with different config
|
|
199
|
+
modelRegistry.clear();
|
|
200
|
+
|
|
201
|
+
// Create schema without graphql.type config
|
|
202
|
+
const parentSchemaNoGraphQL = createMockSchema({
|
|
203
|
+
tenantId: {
|
|
204
|
+
type: String,
|
|
205
|
+
hashKey: true,
|
|
206
|
+
},
|
|
207
|
+
reservationId: {
|
|
208
|
+
type: String,
|
|
209
|
+
rangeKey: true,
|
|
210
|
+
},
|
|
211
|
+
unitId: {
|
|
212
|
+
type: String,
|
|
213
|
+
query: {
|
|
214
|
+
expand: {
|
|
215
|
+
type: "Unit",
|
|
216
|
+
relation: "ONE",
|
|
217
|
+
defaultLimit: 1,
|
|
218
|
+
maxLimit: 1,
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
// No graphql config
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
const unitData = [
|
|
226
|
+
{ tenantId: "tenant-1", unitId: "unit-1", name: "Unit A" },
|
|
227
|
+
];
|
|
228
|
+
|
|
229
|
+
const parentModelNoGraphQL = createMockModel(parentSchemaNoGraphQL);
|
|
230
|
+
const targetModelNoGraphQL = createMockModel(targetModel.schema, unitData);
|
|
231
|
+
|
|
232
|
+
modelRegistry.register("Unit", targetModelNoGraphQL);
|
|
233
|
+
|
|
234
|
+
await resolveExpand({
|
|
235
|
+
parentItems,
|
|
236
|
+
parentModel: parentModelNoGraphQL,
|
|
237
|
+
field: "unitId",
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// Should fallback to field name 'unitId'
|
|
241
|
+
expect(parentItems[0].unitId).toBeDefined();
|
|
242
|
+
expect(parentItems[0].unitId.__typename).toBe("Unit");
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
describe("resolveExpands with GraphQL field mapping", () => {
|
|
247
|
+
let parentModel;
|
|
248
|
+
let targetModel;
|
|
249
|
+
let parentItems;
|
|
250
|
+
|
|
251
|
+
beforeEach(() => {
|
|
252
|
+
modelRegistry.clear();
|
|
253
|
+
|
|
254
|
+
const parentSchema = createMockSchema({
|
|
255
|
+
tenantId: {
|
|
256
|
+
type: String,
|
|
257
|
+
hashKey: true,
|
|
258
|
+
},
|
|
259
|
+
reservationId: {
|
|
260
|
+
type: String,
|
|
261
|
+
rangeKey: true,
|
|
262
|
+
},
|
|
263
|
+
unitId: {
|
|
264
|
+
type: String,
|
|
265
|
+
query: {
|
|
266
|
+
expand: {
|
|
267
|
+
type: "Unit",
|
|
268
|
+
relation: "ONE",
|
|
269
|
+
defaultLimit: 1,
|
|
270
|
+
maxLimit: 1,
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
graphql: {
|
|
274
|
+
type: "Unit",
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
const targetSchema = createMockSchema({
|
|
280
|
+
tenantId: {
|
|
281
|
+
type: String,
|
|
282
|
+
hashKey: true,
|
|
283
|
+
},
|
|
284
|
+
unitId: {
|
|
285
|
+
type: String,
|
|
286
|
+
rangeKey: true,
|
|
287
|
+
},
|
|
288
|
+
name: {
|
|
289
|
+
type: String,
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
const unitData = [
|
|
294
|
+
{ tenantId: "tenant-1", unitId: "unit-1", name: "Unit A" },
|
|
295
|
+
];
|
|
296
|
+
|
|
297
|
+
parentModel = createMockModel(parentSchema);
|
|
298
|
+
targetModel = createMockModel(targetSchema, unitData);
|
|
299
|
+
|
|
300
|
+
modelRegistry.register("Unit", targetModel);
|
|
301
|
+
|
|
302
|
+
parentItems = [
|
|
303
|
+
{
|
|
304
|
+
tenantId: "tenant-1",
|
|
305
|
+
reservationId: "res-1",
|
|
306
|
+
unitId: "unit-1",
|
|
307
|
+
},
|
|
308
|
+
];
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it("should map GraphQL field name (Unit) to schema field name (unitId)", async () => {
|
|
312
|
+
// Simulate GraphQL resolve info with field name 'Unit'
|
|
313
|
+
const fieldsByTypeName = {
|
|
314
|
+
Reservation: {
|
|
315
|
+
Unit: {
|
|
316
|
+
// GraphQL field name
|
|
317
|
+
fieldsByTypeName: {
|
|
318
|
+
Unit: {
|
|
319
|
+
unitId: {},
|
|
320
|
+
name: {},
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
await resolveExpands(parentItems, parentModel, fieldsByTypeName);
|
|
328
|
+
|
|
329
|
+
// Should map 'Unit' -> 'unitId' and store result under 'Unit'
|
|
330
|
+
expect(parentItems[0].Unit).toBeDefined();
|
|
331
|
+
expect(parentItems[0].Unit.__typename).toBe("Unit");
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it("should skip non-expandable fields", async () => {
|
|
335
|
+
const fieldsByTypeName = {
|
|
336
|
+
Reservation: {
|
|
337
|
+
reservationId: {}, // Not an expandable field
|
|
338
|
+
Unit: {
|
|
339
|
+
fieldsByTypeName: {
|
|
340
|
+
Unit: {
|
|
341
|
+
unitId: {},
|
|
342
|
+
name: {},
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
await resolveExpands(parentItems, parentModel, fieldsByTypeName);
|
|
350
|
+
|
|
351
|
+
// Only Unit should be expanded
|
|
352
|
+
expect(parentItems[0].Unit).toBeDefined();
|
|
353
|
+
expect(parentItems[0].reservationId).toBe("res-1"); // Original value unchanged
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
});
|