Skip to content

Commit

Permalink
fix: update deepContains to fail correctly and handle catalogs array
Browse files Browse the repository at this point in the history
  • Loading branch information
dbouwman committed Oct 16, 2024
1 parent 4c8a762 commit 686ed82
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 15 deletions.
45 changes: 44 additions & 1 deletion packages/common/e2e/deep-contains.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ fdescribe("deepContains:", () => {
const initiativeItemId: string = "270b4696648e4e4a8767a1dc9753ae34";
const initiativeAppItemId: string = "c4597275ee874820bf578cdee3106e2f";
const commonAppItemId: string = "63c765456d23439e8faf0e4172fc9b23";
const notContainedItemId: string = "00000000000000000000000000000000";

let factory: Artifactory;
const orgName = "hubBasic";
Expand Down Expand Up @@ -72,6 +73,22 @@ fdescribe("deepContains:", () => {
// tslint:disable-next-line:no-console
console.info(`App in Site Catalog (cached): Time: ${chk2.duration} ms`);
});
it("does not finds item in site catalog", async () => {
const ctxMgr = await factory.getContextManager(orgName, "admin");
const siteCatalogInfo: IDeepCatalogInfo = {
id: siteItemId,
entityType: "item",
};
const chk = await deepContains(
notContainedItemId,
"item",
[siteCatalogInfo],
ctxMgr.context
);
expect(chk.isContained).toBeFalsy();
// tslint:disable-next-line:no-console
console.info(`Item not in Site Catalog: Time: ${chk.duration} ms`);
});

it("finds app in initiative catalog", async () => {
const ctxMgr = await factory.getContextManager(orgName, "admin");
Expand Down Expand Up @@ -122,6 +139,32 @@ fdescribe("deepContains:", () => {
// tslint:disable-next-line:no-console
console.info(`App in Project Catalog: Time: ${chk.duration} ms`);
});
it("does not finds item in project catalog", async () => {
const ctxMgr = await factory.getContextManager(orgName, "admin");
const siteCatalogInfo: IDeepCatalogInfo = {
id: siteItemId,
entityType: "item",
};
const initiativeCatalogInfo: IDeepCatalogInfo = {
id: initiativeItemId,
entityType: "item",
};
const projectCatalogInfo: IDeepCatalogInfo = {
id: projectItemId,
entityType: "item",
};

const chk = await deepContains(
notContainedItemId,
"item",
[projectCatalogInfo, initiativeCatalogInfo, siteCatalogInfo],
ctxMgr.context
);

expect(chk.isContained).toBeFalsy();
// tslint:disable-next-line:no-console
console.info(`Item not in Project Catalog: Time: ${chk.duration} ms`);
});
});
describe("pass in catalogs", () => {
it("finds app in site catalog", async () => {
Expand Down Expand Up @@ -186,7 +229,7 @@ fdescribe("deepContains:", () => {
};

const chk = await deepContains(
initiativeAppItemId,
projectAppItemId,
"item",
[projectCatalogInfo, initiativeCatalogInfo, siteCatalogInfo],
ctxMgr.context
Expand Down
14 changes: 13 additions & 1 deletion packages/common/e2e/hub-sites-contains.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ fdescribe("HubSite.contains:", () => {
const initiativeItemId: string = "270b4696648e4e4a8767a1dc9753ae34";
const initiativeAppItemId: string = "c4597275ee874820bf578cdee3106e2f";
const commonAppItemId: string = "63c765456d23439e8faf0e4172fc9b23";
const notContainedItemId: string = "00000000000000000000000000000000";

let factory: Artifactory;
const orgName = "hubBasic";
Expand Down Expand Up @@ -87,7 +88,7 @@ fdescribe("HubSite.contains:", () => {
`deepCatalogContains: App in Project in Initiative in Site Time: ${response.duration} ms`
);
});
fit("handles site in path levels deep", async () => {
it("handles site in path levels deep", async () => {
const path = `/sites/${siteItemId}/initiatives/${initiativeItemId}/projects/${projectItemId}`;
const id = projectAppItemId;
const response = await deepCatalogContains(id, "content", path, context);
Expand All @@ -97,6 +98,17 @@ fdescribe("HubSite.contains:", () => {
`deepCatalogContains: App in Project in Initiative in Site Time: ${response.duration} ms`
);
});
it("returns false when item is not in deep path", async () => {
const path = `/sites/${siteItemId}/initiatives/${initiativeItemId}/projects/${projectItemId}`;
const id = notContainedItemId;
const response = await deepCatalogContains(id, "content", path, context);

expect(response.isContained).toBeFalsy();
// tslint:disable-next-line:no-console
console.info(
`deepCatalogContains: App in Project in Initiative in Site Time: ${response.duration} ms`
);
});
});

describe(" passing only id:", () => {
Expand Down
11 changes: 10 additions & 1 deletion packages/common/src/core/_internal/deepContains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ export async function deepContains(
if (!hierarchy || hierarchy.length === 0) {
return Promise.resolve(response);
}
// from this point, we assume containment is true
// and only change that if one of the checks fails
response.isContained = true;

// get the id and targetEntity from each of the entries
// in the hiearchy
Expand Down Expand Up @@ -182,7 +185,13 @@ export async function deepContains(
const check = await catalog.contains(currentIdentifier, {
entityType: currentEntityType,
});
response.isContained = check.isContained;
// if a containment check fails, update the response
// we only update it on failure b/c we don't want a positive
// response to override a negative one higher up the hierarchy
if (!check.isContained) {
response.isContained = check.isContained;
response.reason = `Entity ${currentIdentifier} not contained in catalog ${catalogInfo.id}`;
}
});
const end = new Date().getTime();
response.duration = end - start;
Expand Down
4 changes: 2 additions & 2 deletions packages/common/src/core/deepCatalogContains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import { HubEntityType } from "./types/HubEntityType";
* Check that a specific entity is contained within a hierarchy of catalogs
* @param identifier id or slug of the entity to check
* @param hubEntityType Entity type of the identifier
* @param rootCatalog root level catalog to start checking from
* @param context
* @param path
* @param context
* @param rootCatalog root level catalog to start checking from
* @returns
*/
export async function deepCatalogContains(
Expand Down
16 changes: 8 additions & 8 deletions packages/common/src/core/types/HubEntityType.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
export const HUB_ENTITY_TYPES = [
"site",
"project",
"content",
"discussion",
"event",
"group",
"initiative",
"initiativeTemplate",
"page",
"discussion",
"content",
"org",
"group",
"template",
"page",
"project",
"site",
"survey",
"event",
"template",
"user",
] as const;
export type HubEntityType = (typeof HUB_ENTITY_TYPES)[number];
15 changes: 14 additions & 1 deletion packages/common/src/search/fetchCatalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { stripProtocol } from "../urls/strip-protocol";
import { isGuid } from "../utils/is-guid";
import { IHubCatalog } from "./types/IHubCatalog";
import { upgradeCatalogSchema } from "./upgradeCatalogSchema";
import { fetchHubEntity } from "../core/fetchHubEntity";

/**
* Fetch a IHubCatalog from a backing item.
Expand Down Expand Up @@ -40,8 +41,20 @@ export async function fetchCatalog(
});
} else if (isGuid(identifier)) {
// get the item's data, and read the catalog out of it

// TODO: get the entity so the catalog migrations / mappings can be applied
// however this requires the HubEntityType and context, which we don't have
getPrms = getItemData(identifier, requestOptions).then((data) => {
return upgradeCatalogSchema(data.catalog || {});
// catalogs are moving to an array structure so we need to handle both
let catalog = {};
if (data.catalog) {
catalog = data.catalog;
}
if (data.catalogs && data.catalogs.length > 0) {
catalog = data.catalogs[0];
}

return upgradeCatalogSchema(catalog);
});
} else {
throw new HubError("Catalog.create", "Identifier must be a url, guid");
Expand Down
25 changes: 25 additions & 0 deletions packages/common/test/core/_internal/deepContains.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,30 @@ describe("deepContains:", () => {
);
expect(hubSearchSpy).toHaveBeenCalledTimes(2);
});
it("uses catalog if passed and returns contained", async () => {
const fetchCatalogSpy = spyOn(
FetchCatalogModule,
"fetchCatalog"
).and.callFake(() => {
return Promise.resolve(createMockCatalog("ff1"));
});
const hubSearchSpy = spyOn(HubSearchModule, "hubSearch").and.callFake(
() => {
return Promise.resolve({ results: [{ id: AppItemId }] });
}
);

const response = await deepContains(
AppItemId,
"item",
[{ id: "00c", entityType: "item", catalog: createMockCatalog("ff1") }],
context
);
expect(response.identifier).toBe(AppItemId);
expect(response.isContained).toBe(true);
expect(fetchCatalogSpy).toHaveBeenCalledTimes(0);
expect(hubSearchSpy).toHaveBeenCalledTimes(1);
});
it("uses catalog if passed", async () => {
const fetchCatalogSpy = spyOn(
FetchCatalogModule,
Expand All @@ -136,6 +160,7 @@ describe("deepContains:", () => {
);
expect(response.identifier).toBe(AppItemId);
expect(response.isContained).toBe(false);
expect(response.reason).toContain("not contained in catalog");
expect(fetchCatalogSpy).toHaveBeenCalledTimes(0);
expect(hubSearchSpy).toHaveBeenCalledTimes(1);
});
Expand Down
44 changes: 43 additions & 1 deletion packages/common/test/search/fetchCatalog.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IHubRequestOptions } from "../../src";
import { IHubCatalog, IHubRequestOptions } from "../../src";
import { fetchCatalog } from "../../src/search/fetchCatalog";
import { MOCK_AUTH } from "../mocks/mock-auth";
import * as PortalModule from "@esri/arcgis-rest-portal";
Expand Down Expand Up @@ -122,4 +122,46 @@ describe("fetchCatalog:", () => {
const [id, opts] = getItemDataSpy.calls.argsFor(0);
expect(id).toBe("119a6a4051e24a0d956b3006bc2e4bb7");
});
it("looks up by id; catalog array", async () => {
const hro: IHubRequestOptions = {
authentication: MOCK_AUTH,
};

const cat: IHubCatalog = {
schemaVersion: 1,
scopes: {
item: {
targetEntity: "item",
filters: [
{
predicates: [
{
groups: ["00d"],
},
],
},
],
},
},
};

const getItemDataSpy = spyOn(PortalModule, "getItemData").and.callFake(
() => {
return Promise.resolve({
catalogs: [cat, { groups: ["fff"] }],
});
}
);

const chk = await fetchCatalog("119a6a4051e24a0d956b3006bc2e4bb7", hro);
// verify response
expect(chk.schemaVersion).toBe(1);
expect(chk.scopes?.item).toBeDefined();
expect(chk.scopes?.item?.filters.length).toBe(1);
expect(chk.scopes?.item?.filters[0].predicates[0].groups).toEqual(["00d"]);
// verify calls
expect(getItemDataSpy.calls.count()).toBe(1);
const [id, _opts] = getItemDataSpy.calls.argsFor(0);
expect(id).toBe("119a6a4051e24a0d956b3006bc2e4bb7");
});
});

0 comments on commit 686ed82

Please sign in to comment.