diff --git a/lib/Populate.ts b/lib/Populate.ts index 9994714da..580b10d94 100644 --- a/lib/Populate.ts +++ b/lib/Populate.ts @@ -29,7 +29,8 @@ export function PopulateDocument (this: Document, settings?: PopulateSettings | const {model} = this; const localSettings = settings; const promise = model.schemaForObject(this).then((schema) => { - const modelAttributes: any[] = utils.array_flatten(schema.attributes().map((prop) => ({prop, "details": schema.getAttributeTypeDetails(prop)}))).filter((obj) => Array.isArray(obj.details) ? obj.details.some((detail) => detail.name === "Model") : obj.details.name === "Model").map((obj) => obj.prop); + // TODO: uncomment out `/* || detail.name === "Model Set"*/` part and add relevant tests + const modelAttributes: any[] = utils.array_flatten(schema.attributes().map((prop) => ({prop, "details": schema.getAttributeTypeDetails(prop)}))).filter((obj) => Array.isArray(obj.details) ? obj.details.some((detail) => detail.name === "Model"/* || detail.name === "Model Set"*/) : obj.details.name === "Model" || obj.details.name === "Model Set").map((obj) => obj.prop); return {schema, modelAttributes}; }).then((obj: {schema: Schema; modelAttributes: any[]}) => { @@ -42,8 +43,11 @@ export function PopulateDocument (this: Document, settings?: PopulateSettings | // TODO: `subModel` is currently any, we should fix that const subModel = typeof typeSettings.model === "object" ? model.Document as any : typeSettings.model; - const doesPopulatePropertyExist = !(typeof this[prop] === "undefined" || this[prop] === null); - if (!doesPopulatePropertyExist || this[prop] instanceof subModel) { + prop = prop.endsWith(".0") ? prop.substring(0, prop.length - 2) : prop; + + const documentPropValue = utils.object.get(this as any, prop); + const doesPopulatePropertyExist = !(typeof documentPropValue === "undefined" || documentPropValue === null); + if (!doesPopulatePropertyExist || documentPropValue instanceof subModel) { return; } const key: string = [internalSettings.parentKey, prop].filter((a) => Boolean(a)).join("."); @@ -54,9 +58,17 @@ export function PopulateDocument (this: Document, settings?: PopulateSettings | return; } - const subDocument = await subModel.get(this[prop]); - const saveDocument: Document = await PopulateDocument.bind(subDocument)(localSettings, null, {"parentKey": key}); - this[prop] = saveDocument; + const isArray = Array.isArray(documentPropValue); + const isSet = documentPropValue instanceof Set; + if (isArray || isSet) { + const subDocuments = await Promise.all([...documentPropValue as any].map((val) => subModel.get(val))); + const saveDocuments = await Promise.all(subDocuments.map((doc) => PopulateDocument.bind(doc)(localSettings, null, {"parentKey": key}))); + utils.object.set(this as any, prop, saveDocuments); + } else { + const subDocument = await subModel.get(documentPropValue); + const saveDocument: Document = await PopulateDocument.bind(subDocument)(localSettings, null, {"parentKey": key}); + utils.object.set(this as any, prop, saveDocument); + } })); }); diff --git a/lib/Schema.ts b/lib/Schema.ts index cb6cfabf4..71dcd970d 100644 --- a/lib/Schema.ts +++ b/lib/Schema.ts @@ -242,7 +242,7 @@ const attributeTypes: (DynamoDBTypeResult | DynamoDBSetTypeResult)[] = utils.arr type SetValueType = {wrapperName: "Set"; values: ValueType[]; type: string /* TODO: should probably make this an enum */}; type GeneralValueType = string | boolean | number | Buffer | Date; export type ValueType = GeneralValueType | {[key: string]: ValueType} | ValueType[] | SetValueType; -type AttributeType = string | StringConstructor | BooleanConstructor | NumberConstructor | typeof Buffer | DateConstructor | ObjectConstructor | ArrayConstructor | SetConstructor | symbol | Schema; +type AttributeType = string | StringConstructor | BooleanConstructor | NumberConstructor | typeof Buffer | DateConstructor | ObjectConstructor | ArrayConstructor | SetConstructor | symbol | Schema | ModelType; export interface TimestampObject { createdAt?: string | string[]; @@ -540,6 +540,22 @@ export class Schema { utils.object.set(parsedObject, key, newValue); }); + utils.object.entries(parsedObject).forEach((entry) => { + const key = entry[0]; + const value = entry[1] as any; + + if (!key.endsWith(".type") && !key.endsWith(".0")) { + if (value && value.Model && value.Model instanceof Model) { + utils.object.set(parsedObject, key, {"type": value}); + } else if (value && Array.isArray(value)) { + value.forEach((item, index) => { + if (item && item.Model && item.Model instanceof Model) { + utils.object.set(parsedObject, `${key}.${index}`, {"type": item}); + } + }); + } + } + }); // Anytime `this.schemaObject` is modified, `this[internalCache].attributes` must be set to undefined or null this.schemaObject = parsedObject; @@ -837,7 +853,8 @@ Schema.prototype.getAttributeTypeDetails = function (this: Schema, key: string, if (Array.isArray(schemaValue[index])) { schemaValue = schemaValue[index]; } - type = getType(schemaValue[0]); + const subValue = schemaValue[0]; + type = getType(typeof subValue === "object" && subValue.type ? subValue.type : subValue); } const returnObject = retrieveTypeInfo(type, isSet, key, typeSettings); diff --git a/test/types/Schema.ts b/test/types/Schema.ts index 35c99e7e1..da6cb66dd 100644 --- a/test/types/Schema.ts +++ b/test/types/Schema.ts @@ -62,6 +62,11 @@ const shouldSucceedWithConstantType = new dynamoose.Schema({ } }); +const shouldSucceedWithModelAsType = new dynamoose.Schema({ + "id": String, + "item": dynamoose.model("User", {"id": String, "data": String}) +}); + // Nested Schema const shouldSucceedWithNestedSchema = new dynamoose.Schema({ "id": String, diff --git a/test/unit/Model.js b/test/unit/Model.js index 7b4cf6c51..d2a595e98 100644 --- a/test/unit/Model.js +++ b/test/unit/Model.js @@ -1492,6 +1492,230 @@ describe("Model", () => { }); describe("Populate", () => { + it("Should not populate document automatically", async () => { + let getItemTimesCalled = 0; + + User = dynamoose.model("User", {"id": Number, "name": String, "parent": dynamoose.model("Parent", {"id": Number, "data": String})}); + dynamoose.aws.ddb.set({ + "getItem": (params) => { + getItemTimesCalled++; + return {"promise": () => params.Key.id.N === "1" ? {"Item": {"id": {"N": "1"}, "name": {"S": "Charlie"}, "parent": {"N": "2"}}} : {"Item": {"id": {"N": "2"}, "name": {"S": "Bob"}}}}; + } + }); + const user = await callType.func(User).bind(User)(1); + expect(user.toJSON()).to.eql({ + "id": 1, + "name": "Charlie", + "parent": 2 + }); + expect(getItemTimesCalled).to.eql(1); + }); + + it("Should not populate document automatically if schema property is object", async () => { + let getItemTimesCalled = 0; + + User = dynamoose.model("User", {"id": Number, "name": String, "parent": {"type": dynamoose.model("Parent", {"id": Number, "data": String})}}); + dynamoose.aws.ddb.set({ + "getItem": (params) => { + getItemTimesCalled++; + return {"promise": () => params.Key.id.N === "1" ? {"Item": {"id": {"N": "1"}, "name": {"S": "Charlie"}, "parent": {"N": "2"}}} : {"Item": {"id": {"N": "2"}, "name": {"S": "Bob"}}}}; + } + }); + const user = await callType.func(User).bind(User)(1); + expect(user.toJSON()).to.eql({ + "id": 1, + "name": "Charlie", + "parent": 2 + }); + expect(getItemTimesCalled).to.eql(1); + }); + + it("Should not populate document automatically when schema property is dynamoose.THIS", async () => { + let getItemTimesCalled = 0; + + User = dynamoose.model("User", {"id": Number, "name": String, "parent": dynamoose.THIS}); + dynamoose.aws.ddb.set({ + "getItem": (params) => { + getItemTimesCalled++; + return {"promise": () => params.Key.id.N === "1" ? {"Item": {"id": {"N": "1"}, "name": {"S": "Charlie"}, "parent": {"N": "2"}}} : {"Item": {"id": {"N": "2"}, "name": {"S": "Bob"}}}}; + } + }); + const user = await callType.func(User).bind(User)(1); + expect(user.toJSON()).to.eql({ + "id": 1, + "name": "Charlie", + "parent": 2 + }); + expect(getItemTimesCalled).to.eql(1); + }); + + it("Should not populate document automatically when schema property is dynamoose.THIS if schema property is object", async () => { + let getItemTimesCalled = 0; + + User = dynamoose.model("User", {"id": Number, "name": String, "parent": {"type": dynamoose.THIS}}); + dynamoose.aws.ddb.set({ + "getItem": (params) => { + getItemTimesCalled++; + return {"promise": () => params.Key.id.N === "1" ? {"Item": {"id": {"N": "1"}, "name": {"S": "Charlie"}, "parent": {"N": "2"}}} : {"Item": {"id": {"N": "2"}, "name": {"S": "Bob"}}}}; + } + }); + const user = await callType.func(User).bind(User)(1); + expect(user.toJSON()).to.eql({ + "id": 1, + "name": "Charlie", + "parent": 2 + }); + expect(getItemTimesCalled).to.eql(1); + }); + + it("Should not populate document automatically when using set", async () => { + let getItemTimesCalled = 0; + + User = dynamoose.model("User", {"id": Number, "name": String, "parent": {"type": Set, "schema": [dynamoose.model("Parent", {"id": Number, "data": String})]}}); + dynamoose.aws.ddb.set({ + "getItem": (params) => { + getItemTimesCalled++; + return {"promise": () => params.Key.id.N === "1" ? {"Item": {"id": {"N": "1"}, "name": {"S": "Charlie"}, "parent": {"NS": ["2"]}}} : {"Item": {"id": {"N": "2"}, "name": {"S": "Bob"}}}}; + } + }); + const user = await callType.func(User).bind(User)(1); + expect(user.id).to.eql(1); + expect(user.name).to.eql("Charlie"); + expect(user.parent).to.eql(new Set([2])); + expect(Object.keys(user.toJSON())).to.eql(["id", "name", "parent"]); + expect(getItemTimesCalled).to.eql(1); + }); + + it("Should not populate document automatically when using set if schema property is object", async () => { + let getItemTimesCalled = 0; + + User = dynamoose.model("User", {"id": Number, "name": String, "parent": {"type": Set, "schema": [{"type": dynamoose.model("Parent", {"id": Number, "data": String})}]}}); + dynamoose.aws.ddb.set({ + "getItem": (params) => { + getItemTimesCalled++; + return {"promise": () => params.Key.id.N === "1" ? {"Item": {"id": {"N": "1"}, "name": {"S": "Charlie"}, "parent": {"NS": ["2"]}}} : {"Item": {"id": {"N": "2"}, "name": {"S": "Bob"}}}}; + } + }); + const user = await callType.func(User).bind(User)(1); + expect(user.id).to.eql(1); + expect(user.name).to.eql("Charlie"); + expect(user.parent).to.eql(new Set([2])); + expect(Object.keys(user.toJSON())).to.eql(["id", "name", "parent"]); + expect(getItemTimesCalled).to.eql(1); + }); + + it("Should not populate document automatically when using set when schema property is dynamoose.THIS", async () => { + let getItemTimesCalled = 0; + + User = dynamoose.model("User", {"id": Number, "name": String, "parent": {"type": Set, "schema": [dynamoose.THIS]}}); + dynamoose.aws.ddb.set({ + "getItem": (params) => { + getItemTimesCalled++; + return {"promise": () => params.Key.id.N === "1" ? {"Item": {"id": {"N": "1"}, "name": {"S": "Charlie"}, "parent": {"NS": ["2"]}}} : {"Item": {"id": {"N": "2"}, "name": {"S": "Bob"}}}}; + } + }); + const user = await callType.func(User).bind(User)(1); + expect(user.id).to.eql(1); + expect(user.name).to.eql("Charlie"); + expect(user.parent).to.eql(new Set([2])); + expect(Object.keys(user.toJSON())).to.eql(["id", "name", "parent"]); + expect(getItemTimesCalled).to.eql(1); + }); + + it("Should not populate document automatically when using set when schema property is dynamoose.THIS if schema property is object", async () => { + let getItemTimesCalled = 0; + + User = dynamoose.model("User", {"id": Number, "name": String, "parent": {"type": Set, "schema": [{"type": dynamoose.THIS}]}}); + dynamoose.aws.ddb.set({ + "getItem": (params) => { + getItemTimesCalled++; + return {"promise": () => params.Key.id.N === "1" ? {"Item": {"id": {"N": "1"}, "name": {"S": "Charlie"}, "parent": {"NS": ["2"]}}} : {"Item": {"id": {"N": "2"}, "name": {"S": "Bob"}}}}; + } + }); + const user = await callType.func(User).bind(User)(1); + expect(user.id).to.eql(1); + expect(user.name).to.eql("Charlie"); + expect(user.parent).to.eql(new Set([2])); + expect(Object.keys(user.toJSON())).to.eql(["id", "name", "parent"]); + expect(getItemTimesCalled).to.eql(1); + }); + + it("Should not populate document automatically when using array", async () => { + let getItemTimesCalled = 0; + + User = dynamoose.model("User", {"id": Number, "name": String, "parent": {"type": Array, "schema": [dynamoose.model("Parent", {"id": Number, "data": String})]}}); + dynamoose.aws.ddb.set({ + "getItem": (params) => { + getItemTimesCalled++; + return {"promise": () => params.Key.id.N === "1" ? {"Item": {"id": {"N": "1"}, "name": {"S": "Charlie"}, "parent": {"L": [{"N": "2"}]}}} : {"Item": {"id": {"N": "2"}, "name": {"S": "Bob"}}}}; + } + }); + const user = await callType.func(User).bind(User)(1); + expect(user.toJSON()).to.eql({ + "id": 1, + "name": "Charlie", + "parent": [2] + }); + expect(getItemTimesCalled).to.eql(1); + }); + + it("Should not populate document automatically when using array if schema property is object", async () => { + let getItemTimesCalled = 0; + + User = dynamoose.model("User", {"id": Number, "name": String, "parent": {"type": Array, "schema": [{"type": dynamoose.model("Parent", {"id": Number, "data": String})}]}}); + dynamoose.aws.ddb.set({ + "getItem": (params) => { + getItemTimesCalled++; + return {"promise": () => params.Key.id.N === "1" ? {"Item": {"id": {"N": "1"}, "name": {"S": "Charlie"}, "parent": {"L": [{"N": "2"}]}}} : {"Item": {"id": {"N": "2"}, "name": {"S": "Bob"}}}}; + } + }); + const user = await callType.func(User).bind(User)(1); + expect(user.toJSON()).to.eql({ + "id": 1, + "name": "Charlie", + "parent": [2] + }); + expect(getItemTimesCalled).to.eql(1); + }); + + it("Should not populate document automatically when using array when schema property is dynamoose.THIS", async () => { + let getItemTimesCalled = 0; + + User = dynamoose.model("User", {"id": Number, "name": String, "parent": {"type": Array, "schema": [dynamoose.THIS]}}); + dynamoose.aws.ddb.set({ + "getItem": (params) => { + getItemTimesCalled++; + return {"promise": () => params.Key.id.N === "1" ? {"Item": {"id": {"N": "1"}, "name": {"S": "Charlie"}, "parent": {"L": [{"N": "2"}]}}} : {"Item": {"id": {"N": "2"}, "name": {"S": "Bob"}}}}; + } + }); + const user = await callType.func(User).bind(User)(1); + expect(user.toJSON()).to.eql({ + "id": 1, + "name": "Charlie", + "parent": [2] + }); + expect(getItemTimesCalled).to.eql(1); + }); + + it("Should not populate document automatically when using array when schema property is dynamoose.THIS if schema property is object", async () => { + let getItemTimesCalled = 0; + + User = dynamoose.model("User", {"id": Number, "name": String, "parent": {"type": Array, "schema": [{"type": dynamoose.THIS}]}}); + dynamoose.aws.ddb.set({ + "getItem": (params) => { + getItemTimesCalled++; + return {"promise": () => params.Key.id.N === "1" ? {"Item": {"id": {"N": "1"}, "name": {"S": "Charlie"}, "parent": {"L": [{"N": "2"}]}}} : {"Item": {"id": {"N": "2"}, "name": {"S": "Bob"}}}}; + } + }); + const user = await callType.func(User).bind(User)(1); + expect(user.toJSON()).to.eql({ + "id": 1, + "name": "Charlie", + "parent": [2] + }); + expect(getItemTimesCalled).to.eql(1); + }); + it("Should autopopulate if model settings have populate set", async () => { User = dynamoose.model("User", {"id": Number, "name": String, "parent": dynamoose.THIS}, {"populate": "*"}); dynamoose.aws.ddb.set({ diff --git a/test/unit/Query.js b/test/unit/Query.js index 56c3e9a3c..837330980 100644 --- a/test/unit/Query.js +++ b/test/unit/Query.js @@ -613,6 +613,171 @@ describe("Query", () => { expect(response.populate).to.be.a("function"); }); + it("Should populate when calling populate function", async () => { + Model = dynamoose.model("Cat", {"id": Number, "name": {"type": String, "index": {"global": true}}, "parent": dynamoose.THIS}); + dynamoose.aws.ddb.set({ + "getItem": () => { + return {"promise": () => ({"Item": {"id": {"N": "2"}, "name": {"S": "Bob"}}})}; + }, + "query": () => { + return {"promise": () => ({"Items": [{"id": {"N": "1"}, "name": {"S": "Charlie"}, "parent": {"N": "2"}}]})}; + } + }); + const result = await callType.func(Model.query("name").eq("Charlie").exec).bind(Model.query("name").eq("Charlie"))(); + expect(result.toJSON()).to.eql([{ + "id": 1, + "name": "Charlie", + "parent": 2 + }]); + const populatedResult = await result.populate(); + expect(populatedResult.toJSON()).to.eql([{ + "id": 1, + "name": "Charlie", + "parent": { + "id": 2, + "name": "Bob" + } + }]); + }); + + it("Should populate when calling populate function with different model", async () => { + const Model2 = dynamoose.model("Dog", {"id": Number, "name": String}); + Model = dynamoose.model("Cat", {"id": Number, "name": {"type": String, "index": {"global": true}}, "parent": Model2}); + dynamoose.aws.ddb.set({ + "getItem": () => { + return {"promise": () => ({"Item": {"id": {"N": "2"}, "name": {"S": "Bob"}}})}; + }, + "query": () => { + return {"promise": () => ({"Items": [{"id": {"N": "1"}, "name": {"S": "Charlie"}, "parent": {"N": "2"}}]})}; + } + }); + const result = await callType.func(Model.query("name").eq("Charlie").exec).bind(Model.query("name").eq("Charlie"))(); + expect(result.toJSON()).to.eql([{ + "id": 1, + "name": "Charlie", + "parent": 2 + }]); + const populatedResult = await result.populate(); + expect(populatedResult.toJSON()).to.eql([{ + "id": 1, + "name": "Charlie", + "parent": { + "id": 2, + "name": "Bob" + } + }]); + }); + + it("Should populate when calling populate function with array of different models", async () => { + const Model2 = dynamoose.model("Dog", {"id": Number, "name": String}); + Model = dynamoose.model("Cat", {"id": Number, "name": {"type": String, "index": {"global": true}}, "parent": {"type": Array, "schema": [Model2]}}); + dynamoose.aws.ddb.set({ + "getItem": () => { + return {"promise": () => ({"Item": {"id": {"N": "2"}, "name": {"S": "Bob"}}})}; + }, + "query": () => { + return {"promise": () => ({"Items": [{"id": {"N": "1"}, "name": {"S": "Charlie"}, "parent": {"L": [{"N": "2"}]}}]})}; + } + }); + const result = await callType.func(Model.query("name").eq("Charlie").exec).bind(Model.query("name").eq("Charlie"))(); + expect(result.toJSON()).to.eql([{ + "id": 1, + "name": "Charlie", + "parent": [2] + }]); + const populatedResult = await result.populate(); + expect(populatedResult.toJSON()).to.eql([{ + "id": 1, + "name": "Charlie", + "parent": [{ + "id": 2, + "name": "Bob" + }] + }]); + }); + + it("Should populate when calling populate function with array of different models with multiple items", async () => { + const Model2 = dynamoose.model("Dog", {"id": Number, "name": String}); + Model = dynamoose.model("Cat", {"id": Number, "name": {"type": String, "index": {"global": true}}, "parent": {"type": Array, "schema": [Model2]}}); + dynamoose.aws.ddb.set({ + "getItem": (params) => { + return params.Key.id.N === "2" ? {"promise": () => ({"Item": {"id": {"N": "2"}, "name": {"S": "Bob"}}})} : {"promise": () => ({"Item": {"id": {"N": "3"}, "name": {"S": "Tim"}}})}; + }, + "query": () => { + return {"promise": () => ({"Items": [{"id": {"N": "1"}, "name": {"S": "Charlie"}, "parent": {"L": [{"N": "2"}, {"N": "3"}]}}]})}; + } + }); + const result = await callType.func(Model.query("name").eq("Charlie").exec).bind(Model.query("name").eq("Charlie"))(); + expect(result.toJSON()).to.eql([{ + "id": 1, + "name": "Charlie", + "parent": [2, 3] + }]); + const populatedResult = await result.populate(); + expect(populatedResult.toJSON()).to.eql([{ + "id": 1, + "name": "Charlie", + "parent": [{ + "id": 2, + "name": "Bob" + }, { + "id": 3, + "name": "Tim" + }] + }]); + }); + + it("Should populate when calling populate function with set of different models", async () => { + const Model2 = dynamoose.model("Dog", {"id": Number, "name": String}); + Model = dynamoose.model("Cat", {"id": Number, "name": {"type": String, "index": {"global": true}}, "parent": {"type": Set, "schema": [Model2]}}); + dynamoose.aws.ddb.set({ + "getItem": () => { + return {"promise": () => ({"Item": {"id": {"N": "2"}, "name": {"S": "Bob"}}})}; + }, + "query": () => { + return {"promise": () => ({"Items": [{"id": {"N": "1"}, "name": {"S": "Charlie"}, "parent": {"NS": ["2"]}}]})}; + } + }); + const result = await callType.func(Model.query("name").eq("Charlie").exec).bind(Model.query("name").eq("Charlie"))(); + expect(Object.keys(result[0].toJSON())).to.eql(["id", "name", "parent"]); + expect(result[0].toJSON().id).to.eql(1); + expect(result[0].toJSON().name).to.eql("Charlie"); + expect([...result[0].parent]).to.eql([2]); + + const populatedResult = await result.populate(); + expect(Object.keys(populatedResult[0].toJSON())).to.eql(["id", "name", "parent"]); + expect(populatedResult[0].toJSON().id).to.eql(1); + expect(populatedResult[0].toJSON().name).to.eql("Charlie"); + expect(populatedResult[0].parent.length).to.eql(1); + expect([...populatedResult[0].parent][0].toJSON()).to.eql({"id": 2, "name": "Bob"}); + }); + + it("Should populate when calling populate function with set of different models with multiple items", async () => { + const Model2 = dynamoose.model("Dog", {"id": Number, "name": String}); + Model = dynamoose.model("Cat", {"id": Number, "name": {"type": String, "index": {"global": true}}, "parent": {"type": Set, "schema": [Model2]}}); + dynamoose.aws.ddb.set({ + "getItem": (params) => { + return params.Key.id.N === "2" ? {"promise": () => ({"Item": {"id": {"N": "2"}, "name": {"S": "Bob"}}})} : {"promise": () => ({"Item": {"id": {"N": "3"}, "name": {"S": "Tim"}}})}; + }, + "query": () => { + return {"promise": () => ({"Items": [{"id": {"N": "1"}, "name": {"S": "Charlie"}, "parent": {"NS": ["2", "3"]}}]})}; + } + }); + const result = await callType.func(Model.query("name").eq("Charlie").exec).bind(Model.query("name").eq("Charlie"))(); + expect(Object.keys(result[0].toJSON())).to.eql(["id", "name", "parent"]); + expect(result[0].toJSON().id).to.eql(1); + expect(result[0].toJSON().name).to.eql("Charlie"); + expect([...result[0].parent]).to.eql([2, 3]); + + const populatedResult = await result.populate(); + expect(Object.keys(populatedResult[0].toJSON())).to.eql(["id", "name", "parent"]); + expect(populatedResult[0].toJSON().id).to.eql(1); + expect(populatedResult[0].toJSON().name).to.eql("Charlie"); + expect(populatedResult[0].parent.length).to.eql(2); + expect([...populatedResult[0].parent][0].toJSON()).to.eql({"id": 2, "name": "Bob"}); + expect([...populatedResult[0].parent][1].toJSON()).to.eql({"id": 3, "name": "Tim"}); + }); + it("Should autopopulate if model settings have populate set", async () => { Model = dynamoose.model("Cat", {"id": Number, "name": {"type": String, "index": {"global": true}}, "parent": dynamoose.THIS}, {"populate": "*"}); dynamoose.aws.ddb.set({ diff --git a/test/unit/Scan.js b/test/unit/Scan.js index 60e7f816e..54afc55ac 100644 --- a/test/unit/Scan.js +++ b/test/unit/Scan.js @@ -285,6 +285,171 @@ describe("Scan", () => { expect(response.populate).to.be.a("function"); }); + it("Should populate when calling populate function", async () => { + Model = dynamoose.model("Cat", {"id": Number, "name": {"type": String, "index": {"global": true}}, "parent": dynamoose.THIS}); + dynamoose.aws.ddb.set({ + "getItem": () => { + return {"promise": () => ({"Item": {"id": {"N": "2"}, "name": {"S": "Bob"}}})}; + }, + "scan": () => { + return {"promise": () => ({"Items": [{"id": {"N": "1"}, "name": {"S": "Charlie"}, "parent": {"N": "2"}}]})}; + } + }); + const result = await callType.func(Model.scan("name").eq("Charlie").exec).bind(Model.scan("name").eq("Charlie"))(); + expect(result.toJSON()).to.eql([{ + "id": 1, + "name": "Charlie", + "parent": 2 + }]); + const populatedResult = await result.populate(); + expect(populatedResult.toJSON()).to.eql([{ + "id": 1, + "name": "Charlie", + "parent": { + "id": 2, + "name": "Bob" + } + }]); + }); + + it("Should populate when calling populate function with different model", async () => { + const Model2 = dynamoose.model("Dog", {"id": Number, "name": String}); + Model = dynamoose.model("Cat", {"id": Number, "name": {"type": String, "index": {"global": true}}, "parent": Model2}); + dynamoose.aws.ddb.set({ + "getItem": () => { + return {"promise": () => ({"Item": {"id": {"N": "2"}, "name": {"S": "Bob"}}})}; + }, + "scan": () => { + return {"promise": () => ({"Items": [{"id": {"N": "1"}, "name": {"S": "Charlie"}, "parent": {"N": "2"}}]})}; + } + }); + const result = await callType.func(Model.scan("name").eq("Charlie").exec).bind(Model.scan("name").eq("Charlie"))(); + expect(result.toJSON()).to.eql([{ + "id": 1, + "name": "Charlie", + "parent": 2 + }]); + const populatedResult = await result.populate(); + expect(populatedResult.toJSON()).to.eql([{ + "id": 1, + "name": "Charlie", + "parent": { + "id": 2, + "name": "Bob" + } + }]); + }); + + it("Should populate when calling populate function with array of different models", async () => { + const Model2 = dynamoose.model("Dog", {"id": Number, "name": String}); + Model = dynamoose.model("Cat", {"id": Number, "name": {"type": String, "index": {"global": true}}, "parent": {"type": Array, "schema": [Model2]}}); + dynamoose.aws.ddb.set({ + "getItem": () => { + return {"promise": () => ({"Item": {"id": {"N": "2"}, "name": {"S": "Bob"}}})}; + }, + "scan": () => { + return {"promise": () => ({"Items": [{"id": {"N": "1"}, "name": {"S": "Charlie"}, "parent": {"L": [{"N": "2"}]}}]})}; + } + }); + const result = await callType.func(Model.scan("name").eq("Charlie").exec).bind(Model.scan("name").eq("Charlie"))(); + expect(result.toJSON()).to.eql([{ + "id": 1, + "name": "Charlie", + "parent": [2] + }]); + const populatedResult = await result.populate(); + expect(populatedResult.toJSON()).to.eql([{ + "id": 1, + "name": "Charlie", + "parent": [{ + "id": 2, + "name": "Bob" + }] + }]); + }); + + it("Should populate when calling populate function with array of different models with multiple items", async () => { + const Model2 = dynamoose.model("Dog", {"id": Number, "name": String}); + Model = dynamoose.model("Cat", {"id": Number, "name": {"type": String, "index": {"global": true}}, "parent": {"type": Array, "schema": [Model2]}}); + dynamoose.aws.ddb.set({ + "getItem": (params) => { + return params.Key.id.N === "2" ? {"promise": () => ({"Item": {"id": {"N": "2"}, "name": {"S": "Bob"}}})} : {"promise": () => ({"Item": {"id": {"N": "3"}, "name": {"S": "Tim"}}})}; + }, + "scan": () => { + return {"promise": () => ({"Items": [{"id": {"N": "1"}, "name": {"S": "Charlie"}, "parent": {"L": [{"N": "2"}, {"N": "3"}]}}]})}; + } + }); + const result = await callType.func(Model.scan("name").eq("Charlie").exec).bind(Model.scan("name").eq("Charlie"))(); + expect(result.toJSON()).to.eql([{ + "id": 1, + "name": "Charlie", + "parent": [2, 3] + }]); + const populatedResult = await result.populate(); + expect(populatedResult.toJSON()).to.eql([{ + "id": 1, + "name": "Charlie", + "parent": [{ + "id": 2, + "name": "Bob" + }, { + "id": 3, + "name": "Tim" + }] + }]); + }); + + it("Should populate when calling populate function with set of different models", async () => { + const Model2 = dynamoose.model("Dog", {"id": Number, "name": String}); + Model = dynamoose.model("Cat", {"id": Number, "name": {"type": String, "index": {"global": true}}, "parent": {"type": Set, "schema": [Model2]}}); + dynamoose.aws.ddb.set({ + "getItem": () => { + return {"promise": () => ({"Item": {"id": {"N": "2"}, "name": {"S": "Bob"}}})}; + }, + "scan": () => { + return {"promise": () => ({"Items": [{"id": {"N": "1"}, "name": {"S": "Charlie"}, "parent": {"NS": ["2"]}}]})}; + } + }); + const result = await callType.func(Model.scan("name").eq("Charlie").exec).bind(Model.scan("name").eq("Charlie"))(); + expect(Object.keys(result[0].toJSON())).to.eql(["id", "name", "parent"]); + expect(result[0].toJSON().id).to.eql(1); + expect(result[0].toJSON().name).to.eql("Charlie"); + expect([...result[0].parent]).to.eql([2]); + + const populatedResult = await result.populate(); + expect(Object.keys(populatedResult[0].toJSON())).to.eql(["id", "name", "parent"]); + expect(populatedResult[0].toJSON().id).to.eql(1); + expect(populatedResult[0].toJSON().name).to.eql("Charlie"); + expect(populatedResult[0].parent.length).to.eql(1); + expect([...populatedResult[0].parent][0].toJSON()).to.eql({"id": 2, "name": "Bob"}); + }); + + it("Should populate when calling populate function with set of different models with multiple items", async () => { + const Model2 = dynamoose.model("Dog", {"id": Number, "name": String}); + Model = dynamoose.model("Cat", {"id": Number, "name": {"type": String, "index": {"global": true}}, "parent": {"type": Set, "schema": [Model2]}}); + dynamoose.aws.ddb.set({ + "getItem": (params) => { + return params.Key.id.N === "2" ? {"promise": () => ({"Item": {"id": {"N": "2"}, "name": {"S": "Bob"}}})} : {"promise": () => ({"Item": {"id": {"N": "3"}, "name": {"S": "Tim"}}})}; + }, + "scan": () => { + return {"promise": () => ({"Items": [{"id": {"N": "1"}, "name": {"S": "Charlie"}, "parent": {"NS": ["2", "3"]}}]})}; + } + }); + const result = await callType.func(Model.scan("name").eq("Charlie").exec).bind(Model.scan("name").eq("Charlie"))(); + expect(Object.keys(result[0].toJSON())).to.eql(["id", "name", "parent"]); + expect(result[0].toJSON().id).to.eql(1); + expect(result[0].toJSON().name).to.eql("Charlie"); + expect([...result[0].parent]).to.eql([2, 3]); + + const populatedResult = await result.populate(); + expect(Object.keys(populatedResult[0].toJSON())).to.eql(["id", "name", "parent"]); + expect(populatedResult[0].toJSON().id).to.eql(1); + expect(populatedResult[0].toJSON().name).to.eql("Charlie"); + expect(populatedResult[0].parent.length).to.eql(2); + expect([...populatedResult[0].parent][0].toJSON()).to.eql({"id": 2, "name": "Bob"}); + expect([...populatedResult[0].parent][1].toJSON()).to.eql({"id": 3, "name": "Tim"}); + }); + it("Should autopopulate if model settings have populate set", async () => { Model = dynamoose.model("Cat", {"id": Number, "name": String, "parent": dynamoose.THIS}, {"populate": "*"}); dynamoose.aws.ddb.set({ diff --git a/test/unit/_setup.js b/test/unit/_setup.js new file mode 100644 index 000000000..b47068d21 --- /dev/null +++ b/test/unit/_setup.js @@ -0,0 +1,5 @@ +const ModelStore = require("../../dist/ModelStore"); + +beforeEach(() => { + ModelStore.clear(); +});