Skip to content

Commit

Permalink
Fix(SCIMMY.Types.Definition): correctly cast arrays when handling nam…
Browse files Browse the repository at this point in the history
…espaced attribute values during coercion
  • Loading branch information
sleelin committed Nov 15, 2023
1 parent 812f437 commit b07cc70
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 4 deletions.
13 changes: 11 additions & 2 deletions src/lib/types/definition.js
Original file line number Diff line number Diff line change
Expand Up @@ -281,8 +281,17 @@ export class SchemaDefinition {
// Merge all properties from s into t, joining arrays and objects
for (let skey of Object.keys(s)) {
const tkey = skey.toLowerCase();
if (Array.isArray(t[tkey]) && Array.isArray(s[skey])) t[tkey].push(...s[skey]);

// If source is an array...
if (Array.isArray(s[skey])) {
// ...and target is an array, merge them...
if (Array.isArray(t[tkey])) t[tkey].push(...s[skey]);
// ...otherwise, make target an array
else t[tkey] = [...s[skey]];
}
// If source is a primitive value, copy it
else if (s[skey] !== Object(s[skey])) t[tkey] = s[skey];
// Finally, if source is neither an array nor primitive, merge it
else t[tkey] = merge(t[tkey] ?? {}, s[skey]);
}

Expand All @@ -297,7 +306,7 @@ export class SchemaDefinition {
// Coerce the mixed value, using only namespaced attributes for this extension
target[name] = attribute.coerce(mixedSource, direction, basepath, [Object.keys(filter ?? {})
.filter(k => k.startsWith(`${name}:`))
.reduce((res, key) => (((res[key.replace(`${name}:`, "")] = filter[key]) || true) && res), {})
.reduce((res, key) => Object.assign(res, {[key.replace(`${name}:`, "")]: filter[key]}), {})
]);
} catch (ex) {
// Rethrow exception with added context
Expand Down
28 changes: 26 additions & 2 deletions test/hooks/schemas.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,34 @@ export default {
}
});

// https://github.com/scimmyjs/scimmy/issues/12
it("should coerce complex multi-value attributes in schema extensions", async () => {
const {constructor = {}} = await fixtures;
const subAttribute = new Attribute("string", "name");
const attribute = new Attribute("complex", "agencies", {multiValued: true}, [subAttribute]);
const extension = new SchemaDefinition("Extension", "urn:ietf:params:scim:schemas:Extension", "", [attribute]);
const source = {...constructor, [extension.id]: {[attribute.name]: [{[subAttribute.name]: "value"}]}};

try {
// Add the extension to the target
TargetSchema.extend(extension);

// Construct an instance to test against, and get actual value for comparison
const instance = new TargetSchema(source);
const actual = JSON.parse(JSON.stringify(instance[extension.id][attribute.name]));

assert.deepStrictEqual(actual, source[extension.id][attribute.name],
"Schema instance did not coerce complex multi-value attributes from schema extension");
} finally {
// Remove the extension so it doesn't interfere later
TargetSchema.truncate("urn:ietf:params:scim:schemas:Extension");
}
});

it("should expect errors in extension schema coercion to be rethrown as SCIMErrors", async () => {
const {constructor = {}} = await fixtures;
const attributes = [new Attribute("string", "testValue")];
const extension = new SchemaDefinition("Extension", "urn:ietf:params:scim:schemas:Extension", "", attributes);
const {constructor = {}} = await fixtures;
const source = {...constructor, [`${extension.id}:testValue`]: "a string"};

try {
Expand Down Expand Up @@ -145,8 +169,8 @@ export default {
];

// Get the extension and the source data ready
const extension = new SchemaDefinition("Extension", "urn:ietf:params:scim:schemas:Extension", "", attributes);
const {constructor = {}} = await fixtures;
const extension = new SchemaDefinition("Extension", "urn:ietf:params:scim:schemas:Extension", "", attributes);
const source = {
...constructor,
[`${extension.id}:testValue.stringValue`]: "a string",
Expand Down

0 comments on commit b07cc70

Please sign in to comment.