Skip to content

Commit 4f6f576

Browse files
authored
Update multiple commands to clear context under certain circumstances. (#208)
* Updated auth commands to clear context when appropriate. * Updated 'itwin delete' and 'imodel delete' commands to update context when saved itwin/imodel is deleted. * Added integration tests. * Added more mocked integration tests. * Added missing copyright notices to files. * Removed nock usage from integration tests. * Updated a test. * Fixed auth integration tests. * Attempt to fix auth integration tests. * Updated context clear on logout tests. * Updated auth integration tests. * Updated auth integration tests.
1 parent f940580 commit 4f6f576

22 files changed

Lines changed: 574 additions & 10 deletions

File tree

integration-tests/auth-native/auth-native.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ const tests = () =>
3333
const { error } = await runCommand<AuthorizationInformation>("user me");
3434
expect(error).to.not.be.undefined;
3535
expect(error?.message).to.be.equal("Interactive auth token has expired. Please run 'itp auth login' command to re-authenticate.");
36+
37+
await runCommand("auth logout");
3638
});
3739

3840
after(async () => {

integration-tests/auth-service/auth-service.test.ts

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,35 @@ import fs from "node:fs";
99
import { runCommand } from "@oclif/test";
1010

1111
import { AuthorizationInformation } from "../../src/services/authorization/authorization-type";
12+
import { ResultResponse } from "../../src/services/general-models/result-response";
13+
import { UserContext } from "../../src/services/general-models/user-context";
1214
import { ITP_API_URL, ITP_ISSUER_URL, ITP_SERVICE_CLIENT_ID, ITP_SERVICE_CLIENT_SECRET } from "../utils/environment";
13-
import { getTokenPathByOS, serviceLoginToCli } from "../utils/helpers";
15+
import { createIModel, createITwin, getTokenPathByOS, serviceLoginToCli } from "../utils/helpers";
1416
import runSuiteIfMainModule from "../utils/run-suite-if-main-module";
1517

1618
const tests = () =>
1719
describe("Authentication Integration Tests (Service Client)", () => {
20+
const testIModelName = `cli-imodel-integration-test-${new Date().toISOString()}`;
21+
let testIModelId: string;
22+
let testITwinId: string;
23+
24+
before(async () => {
25+
const testITwin = await createITwin(`cli-itwin-integration-test-${new Date().toISOString()}`, "Thing", "Asset");
26+
testITwinId = testITwin.id as string;
27+
const testIModel = await createIModel(testIModelName, testITwinId);
28+
testIModelId = testIModel.id;
29+
});
30+
31+
after(async () => {
32+
await serviceLoginToCli();
33+
34+
const { result: imodelDeleteResult } = await runCommand<ResultResponse>(`imodel delete --imodel-id ${testIModelId}`);
35+
const { result: itwinDeleteResult } = await runCommand<ResultResponse>(`itwin delete --itwin-id ${testITwinId}`);
36+
37+
expect(imodelDeleteResult).to.have.property("result", "deleted");
38+
expect(itwinDeleteResult).to.have.property("result", "deleted");
39+
});
40+
1841
it("auth info get full auth info from token when logged in", async () => {
1942
await serviceLoginToCli();
2043

@@ -63,8 +86,20 @@ const tests = () =>
6386
process.env.ITP_SERVICE_CLIENT_SECRET = serviceClientSecret;
6487
});
6588

66-
after(async () => {
89+
it("should preserve context when user logs into the same service account", async () => {
6790
await serviceLoginToCli();
91+
92+
const { result: contextBefore } = await runCommand<UserContext>(`context set -i ${testITwinId} -m ${testIModelId}`);
93+
expect(contextBefore).to.not.be.undefined;
94+
expect(contextBefore?.iTwinId).to.be.equal(testITwinId);
95+
expect(contextBefore?.iModelId).to.be.equal(testIModelId);
96+
97+
await serviceLoginToCli();
98+
99+
const { result: contextAfter } = await runCommand<UserContext>(`context info`);
100+
expect(contextAfter).to.not.be.undefined;
101+
expect(contextAfter?.iTwinId).to.be.equal(testITwinId);
102+
expect(contextAfter?.iModelId).to.be.equal(testIModelId);
68103
});
69104
});
70105

integration-tests/auth/auth.test.ts

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,43 @@ import { expect } from "chai";
77

88
import { runCommand } from "@oclif/test";
99

10-
import { AuthorizationInformation } from "../../src/services/authorization/authorization-type";
10+
import { AuthorizationInformation, AuthorizationType } from "../../src/services/authorization/authorization-type";
11+
import { ResultResponse } from "../../src/services/general-models/result-response";
12+
import { UserContext } from "../../src/services/general-models/user-context.js";
1113
import { ITP_API_URL, ITP_ISSUER_URL } from "../utils/environment";
14+
import { createIModel, createITwin, getCurrentTokenType, nativeLoginToCli, serviceLoginToCli } from "../utils/helpers";
1215
import runSuiteIfMainModule from "../utils/run-suite-if-main-module";
1316

1417
const tests = () =>
1518
describe("Authentication Integration Tests", () => {
19+
const testIModelName = `cli-imodel-integration-test-${new Date().toISOString()}`;
20+
let authType: AuthorizationType;
21+
let testIModelId: string;
22+
let testITwinId: string;
23+
24+
before(async () => {
25+
const testITwin = await createITwin(`cli-itwin-integration-test-${new Date().toISOString()}`, "Thing", "Asset");
26+
testITwinId = testITwin.id as string;
27+
const testIModel = await createIModel(testIModelName, testITwinId);
28+
testIModelId = testIModel.id;
29+
30+
authType = getCurrentTokenType() as AuthorizationType;
31+
});
32+
33+
after(async () => {
34+
if (authType === AuthorizationType.Service) {
35+
await serviceLoginToCli();
36+
} else {
37+
await nativeLoginToCli();
38+
}
39+
40+
const { result: imodelDeleteResult } = await runCommand<ResultResponse>(`imodel delete --imodel-id ${testIModelId}`);
41+
const { result: itwinDeleteResult } = await runCommand<ResultResponse>(`itwin delete --itwin-id ${testITwinId}`);
42+
43+
expect(imodelDeleteResult).to.have.property("result", "deleted");
44+
expect(itwinDeleteResult).to.have.property("result", "deleted");
45+
});
46+
1647
it("auth info should get urls from environment when not logged in", async () => {
1748
const apiUrl = ITP_API_URL;
1849
const issuerUrl = ITP_ISSUER_URL;
@@ -53,6 +84,24 @@ const tests = () =>
5384

5485
expect(stdout).to.include("User successfully logged out");
5586
});
87+
88+
it("should clear context when user logs out", async () => {
89+
if (authType === AuthorizationType.Service) {
90+
await serviceLoginToCli();
91+
} else {
92+
await nativeLoginToCli();
93+
}
94+
95+
const { result: contextBefore } = await runCommand<UserContext>(`context set -i ${testITwinId} -m ${testIModelId}`);
96+
expect(contextBefore).to.not.be.undefined;
97+
expect(contextBefore?.iTwinId).to.be.equal(testITwinId);
98+
expect(contextBefore?.iModelId).to.be.equal(testIModelId);
99+
100+
await runCommand("auth logout");
101+
102+
const { result: contextAfter } = await runCommand<UserContext>(`context set -i ${testITwinId} -m ${testIModelId}`);
103+
expect(contextAfter).to.be.undefined;
104+
});
56105
});
57106

58107
export default tests;

integration-tests/imodel/create-delete.test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { IModel } from "@itwin/imodels-client-management";
99
import { runCommand } from "@oclif/test";
1010

1111
import { ResultResponse } from "../../src/services/general-models/result-response";
12+
import { UserContext } from "../../src/services/general-models/user-context";
1213
import { createIModel, createITwin, deleteIModel } from "../utils/helpers";
1314
import runSuiteIfMainModule from "../utils/run-suite-if-main-module";
1415

@@ -78,6 +79,54 @@ const tests = () =>
7879
expect(createdIModel!.extent).to.be.deep.equal(extent);
7980
});
8081

82+
it("Should remove imodel from context when it is deleted", async () => {
83+
const iModelName = `${testIModelName}-context-test1`;
84+
const { result: createdIModel } = await runCommand<IModel>(`imodel create --itwin-id ${testITwinId} --name "${iModelName}" --save`);
85+
expect(createdIModel).to.not.be.undefined;
86+
87+
await runCommand<UserContext>(`context set -m ${createdIModel!.id}`);
88+
89+
const { result: contextBefore } = await runCommand<UserContext>(`context info`);
90+
expect(contextBefore).to.not.be.undefined;
91+
expect(contextBefore?.iTwinId).to.be.equal(testITwinId);
92+
expect(contextBefore?.iModelId).to.be.equal(createdIModel?.id);
93+
94+
const { result: deleteResult } = await runCommand<ResultResponse>(`imodel delete -m ${createdIModel?.id}`);
95+
expect(deleteResult).to.not.be.undefined;
96+
expect(deleteResult).to.have.property("result", "deleted");
97+
98+
const { result: contextAfter } = await runCommand<UserContext>(`context info`);
99+
expect(contextAfter).to.not.be.undefined;
100+
expect(contextAfter?.iTwinId).to.be.equal(testITwinId);
101+
expect(contextAfter?.iModelId).to.be.undefined;
102+
});
103+
104+
it("Should not remove imodel from context when a different iModel is deleted", async () => {
105+
const iModelName1 = `${testIModelName}-context-test1`;
106+
const { result: createdIModel1 } = await runCommand<IModel>(`imodel create --itwin-id ${testITwinId} --name "${iModelName1}" --save`);
107+
expect(createdIModel1).to.not.be.undefined;
108+
109+
const iModelName2 = `${testIModelName}-context-test2`;
110+
const { result: createdIModel2 } = await runCommand<IModel>(`imodel create --itwin-id ${testITwinId} --name "${iModelName2}" --save`);
111+
expect(createdIModel2).to.not.be.undefined;
112+
113+
await runCommand<UserContext>(`context set -m ${createdIModel1!.id}`);
114+
115+
const { result: contextBefore } = await runCommand<UserContext>(`context info`);
116+
expect(contextBefore).to.not.be.undefined;
117+
expect(contextBefore?.iTwinId).to.be.equal(testITwinId);
118+
expect(contextBefore?.iModelId).to.be.equal(createdIModel1?.id);
119+
120+
const { result: deleteResult } = await runCommand<ResultResponse>(`imodel delete -m ${createdIModel2?.id}`);
121+
expect(deleteResult).to.not.be.undefined;
122+
expect(deleteResult).to.have.property("result", "deleted");
123+
124+
const { result: contextAfter } = await runCommand<UserContext>(`context info`);
125+
expect(contextAfter).to.not.be.undefined;
126+
expect(contextAfter?.iTwinId).to.be.equal(testITwinId);
127+
expect(contextAfter?.iModelId).to.be.equal(createdIModel1?.id);
128+
});
129+
81130
it("should return an error if user provides extent in both ways", async () => {
82131
const extent = {
83132
northEast: {

integration-tests/itwin/delete.test.ts

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,17 @@ import { ITwin } from "@itwin/itwins-client";
99
import { runCommand } from "@oclif/test";
1010

1111
import { ResultResponse } from "../../src/services/general-models/result-response";
12+
import { UserContext } from "../../src/services/general-models/user-context";
1213
import { createITwin } from "../utils/helpers";
1314
import runSuiteIfMainModule from "../utils/run-suite-if-main-module";
1415

1516
const tests = () =>
1617
describe("delete", () => {
18+
const testITwinName = `cli-itwin-integration-test-${new Date().toISOString()}`;
1719
let testITwin: ITwin;
1820

1921
before(async () => {
20-
testITwin = await createITwin(`cli-itwin-integration-test-${new Date().toISOString()}`, "Thing", "Asset");
22+
testITwin = await createITwin(testITwinName, "Thing", "Asset");
2123
});
2224

2325
it("should delete the iTwin", async () => {
@@ -29,6 +31,58 @@ const tests = () =>
2931
expect(errorResult!.message).to.include("iTwinNotFound");
3032
});
3133

34+
it("should clear context, when saved iTwin is deleted", async () => {
35+
const iTwinName = `${testITwinName}-context-test1`;
36+
37+
const { result: createdITwin } = await runCommand<ITwin>(`itwin create --name "${iTwinName}" --class Thing --sub-class Asset --save`);
38+
39+
await runCommand<UserContext>(`context set -i ${createdITwin!.id}`);
40+
const { result: contextBefore } = await runCommand<UserContext>(`context info`);
41+
42+
const { result: deleteResult } = await runCommand<ResultResponse>(`itwin delete -i ${createdITwin?.id}`);
43+
const { result: contextAfter } = await runCommand<UserContext>(`context info`);
44+
45+
expect(createdITwin).to.not.be.undefined;
46+
47+
expect(contextBefore).to.not.be.undefined;
48+
expect(contextBefore?.iTwinId).to.be.equal(createdITwin?.id);
49+
expect(contextBefore?.iModelId).to.be.undefined;
50+
51+
expect(deleteResult).to.not.be.undefined;
52+
expect(deleteResult).to.have.property("result", "deleted");
53+
expect(contextAfter).to.be.undefined;
54+
});
55+
56+
it("should not clear context, when a different iTwin is deleted", async () => {
57+
const iTwinName1 = `${testITwinName}-context-test1`;
58+
const iTwinName2 = `${testITwinName}-context-test2`;
59+
60+
const { result: createdITwin1 } = await runCommand<ITwin>(`itwin create --name "${iTwinName1}" --class Thing --sub-class Asset --save`);
61+
const { result: createdITwin2 } = await runCommand<ITwin>(`itwin create --name "${iTwinName2}" --class Thing --sub-class Asset --save`);
62+
63+
await runCommand<UserContext>(`context set -i ${createdITwin1!.id}`);
64+
const { result: contextBefore } = await runCommand<UserContext>(`context info`);
65+
66+
const { result: deleteResult2 } = await runCommand<ResultResponse>(`itwin delete -i ${createdITwin2?.id}`);
67+
const { result: contextAfter } = await runCommand<UserContext>(`context info`);
68+
const { result: deleteResult1 } = await runCommand<ResultResponse>(`itwin delete -i ${createdITwin1?.id}`);
69+
70+
expect(createdITwin1).to.not.be.undefined;
71+
expect(createdITwin2).to.not.be.undefined;
72+
73+
expect(contextBefore).to.not.be.undefined;
74+
expect(contextBefore?.iTwinId).to.be.equal(createdITwin1?.id);
75+
expect(contextBefore?.iModelId).to.be.undefined;
76+
77+
expect(deleteResult2).to.not.be.undefined;
78+
expect(deleteResult2).to.have.property("result", "deleted");
79+
expect(contextAfter).to.not.be.undefined;
80+
expect(contextAfter?.iTwinId).to.be.equal(createdITwin1?.id);
81+
expect(contextAfter?.iModelId).to.be.undefined;
82+
expect(deleteResult1).to.not.be.undefined;
83+
expect(deleteResult1).to.have.property("result", "deleted");
84+
});
85+
3286
it("should return an error when invalid uuid is provided as --itwin-id", async () => {
3387
const { error } = await runCommand<ITwin>(`itwin delete --itwin-id an-invalid-uuid`);
3488
expect(error?.message).to.contain("'an-invalid-uuid' is not a valid UUID.");
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
3+
* See LICENSE.md in the project root for license terms and full copyright notice.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { expect } from "chai";
7+
8+
import { runCommand } from "@oclif/test";
9+
10+
import runSuiteIfMainModule from "../../integration-tests/utils/run-suite-if-main-module.js";
11+
import { AuthorizationType } from "../../src/services/authorization/authorization-type.js";
12+
import { UserContext } from "../../src/services/general-models/user-context.js";
13+
import { IModelsApiMock } from "../utils/api-mocks/imodels-api/imodels-api-mock.js";
14+
import { writeMockToken } from "../utils/helpers.js";
15+
16+
const tests = () =>
17+
describe("auth", () => {
18+
const testIModelId = crypto.randomUUID();
19+
const testITwinId = crypto.randomUUID();
20+
21+
after(async () => {
22+
writeMockToken("mock-client", AuthorizationType.Service);
23+
});
24+
25+
it("should clear context when user logs out", async () => {
26+
IModelsApiMock.getIModel.success(testITwinId, testIModelId);
27+
28+
await runCommand<UserContext>(`context set -i ${testITwinId} -m ${testIModelId}`);
29+
30+
const { result: contextBefore } = await runCommand<UserContext>(`context info`);
31+
expect(contextBefore).to.not.be.undefined;
32+
expect(contextBefore?.iTwinId).to.be.equal(testITwinId);
33+
expect(contextBefore?.iModelId).to.be.equal(testIModelId);
34+
35+
await runCommand("auth logout");
36+
37+
const { result: contextAfter } = await runCommand<UserContext>(`context info`);
38+
expect(contextAfter).to.be.undefined;
39+
});
40+
});
41+
42+
export default tests;
43+
44+
runSuiteIfMainModule(import.meta, tests);
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
3+
* See LICENSE.md in the project root for license terms and full copyright notice.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { expect } from "chai";
7+
8+
import { runCommand } from "@oclif/test";
9+
10+
import runSuiteIfMainModule from "../../integration-tests/utils/run-suite-if-main-module.js";
11+
import { ResultResponse } from "../../src/services/general-models/result-response.js";
12+
import { UserContext } from "../../src/services/general-models/user-context.js";
13+
import { IModelsApiMock } from "../utils/api-mocks/imodels-api/imodels-api-mock.js";
14+
15+
const tests = () =>
16+
describe("delete", () => {
17+
const testIModelId1 = crypto.randomUUID();
18+
const testIModelId2 = crypto.randomUUID();
19+
const testITwinId = crypto.randomUUID();
20+
21+
it("Should remove imodel from context when it is deleted", async () => {
22+
IModelsApiMock.getIModel.success(testITwinId, testIModelId1);
23+
IModelsApiMock.deleteIModel.success(testIModelId1);
24+
25+
await runCommand<UserContext>(`context set -m ${testIModelId1}`);
26+
27+
const { result: contextBefore } = await runCommand<UserContext>(`context info`);
28+
expect(contextBefore).to.not.be.undefined;
29+
expect(contextBefore?.iTwinId).to.be.equal(testITwinId);
30+
expect(contextBefore?.iModelId).to.be.equal(testIModelId1);
31+
32+
const { result: deleteResult } = await runCommand<ResultResponse>(`imodel delete -m ${testIModelId1}`);
33+
expect(deleteResult).to.not.be.undefined;
34+
expect(deleteResult).to.have.property("result", "deleted");
35+
36+
const { result: contextAfter } = await runCommand<UserContext>(`context info`);
37+
expect(contextAfter).to.not.be.undefined;
38+
expect(contextAfter?.iTwinId).to.be.equal(testITwinId);
39+
expect(contextAfter?.iModelId).to.be.undefined;
40+
});
41+
42+
it("Should not remove imodel from context when a different iModel is deleted", async () => {
43+
IModelsApiMock.getIModel.success(testITwinId, testIModelId1);
44+
IModelsApiMock.deleteIModel.success(testIModelId2);
45+
46+
await runCommand<UserContext>(`context set -m ${testIModelId1}`);
47+
48+
const { result: contextBefore } = await runCommand<UserContext>(`context info`);
49+
expect(contextBefore).to.not.be.undefined;
50+
expect(contextBefore?.iTwinId).to.be.equal(testITwinId);
51+
expect(contextBefore?.iModelId).to.be.equal(testIModelId1);
52+
53+
const { result: deleteResult } = await runCommand<ResultResponse>(`imodel delete -m ${testIModelId2}`);
54+
expect(deleteResult).to.not.be.undefined;
55+
expect(deleteResult).to.have.property("result", "deleted");
56+
57+
const { result: contextAfter } = await runCommand<UserContext>(`context info`);
58+
expect(contextAfter).to.not.be.undefined;
59+
expect(contextAfter?.iTwinId).to.be.equal(testITwinId);
60+
expect(contextAfter?.iModelId).to.be.equal(testIModelId1);
61+
});
62+
});
63+
64+
export default tests;
65+
66+
runSuiteIfMainModule(import.meta, tests);

0 commit comments

Comments
 (0)