Skip to content

Commit dae9956

Browse files
authored
Adding itp api command integration tests (#96)
1 parent fbcf033 commit dae9956

4 files changed

Lines changed: 157 additions & 2 deletions

File tree

.vscode/launch.json

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,40 @@
1717
"--itwin-id",
1818
"<itwin-id>"
1919
]
20+
},
21+
{
22+
"name": "Debug All Tests",
23+
"skipFiles": [
24+
"<node_internals>/**"
25+
],
26+
"type": "node",
27+
"request": "launch",
28+
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/mocha",
29+
"args": [
30+
"--forbid-only",
31+
"--timeout",
32+
"999999",
33+
"integration-tests/**/api.test.ts"
34+
],
35+
"console": "integratedTerminal",
36+
"internalConsoleOptions": "neverOpen"
37+
},
38+
{
39+
"name": "Debug Active Test",
40+
"skipFiles": [
41+
"<node_internals>/**"
42+
],
43+
"type": "node",
44+
"request": "launch",
45+
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/mocha",
46+
"args": [
47+
"--forbid-only",
48+
"--timeout",
49+
"999999",
50+
"${file}"
51+
],
52+
"console": "integratedTerminal",
53+
"internalConsoleOptions": "neverOpen"
2054
}
2155
]
2256
}

docs/api.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ itp api --method DELETE --path itwins/favorites/dc914a84-e0c9-40e2-9d14-faf5ed84
4848
# Example 4: Sending a post request
4949
itp api --method POST --path itwins/exports --body '{"outputFormat": "JsonGZip"}'
5050

51+
# Example 5: Sending a post request (Windows PowerShell)
52+
itp api --method POST --path users/getbyidlist --body "[\`"b644de17-f07e-4b43-8c33-ad2b1bacee3b\`"]"
53+
5154
```
5255

5356
## APIs Docs Reference

integration-tests/api.test.ts

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { IModel } from "@itwin/imodels-client-management";
2+
import { ITwin } from "@itwin/itwins-client";
3+
import { runCommand } from "@oclif/test";
4+
import { expect } from "chai";
5+
6+
import { User } from "../src/services/user-client/models/user.js";
7+
8+
describe("API Integration Tests", () => {
9+
let iTwin: ITwin;
10+
let iModel: IModel;
11+
12+
before(async () => {
13+
const date = new Date();
14+
const iTwinRespone = await runCommand<ITwin>(`itwin create --class Thing --sub-class Asset --name itwin-cli-test-${date.toISOString()}`);
15+
expect(iTwinRespone.error).to.be.undefined;
16+
iTwin = iTwinRespone.result!;
17+
const iModelResponse = await runCommand<IModel>(`imodel create --name imodel-cli-test-${date.toISOString()} --itwin-id ${iTwin.id}`);
18+
expect(iModelResponse.error).to.be.undefined;
19+
iModel = iModelResponse.result!;
20+
});
21+
22+
after(async () => {
23+
await runCommand(`itwin delete -i ${iTwin.id}`);
24+
});
25+
26+
it("should send a GET request and get user me info", async () => {
27+
const apiResponse = await runCommand("api --method GET --path users/me");
28+
expect(apiResponse.error).to.be.undefined;
29+
const userApiInfo = JSON.parse(apiResponse.stdout);
30+
31+
const userCommandInfo = await runCommand<User>("user me");
32+
33+
expect(userApiInfo).to.have.property("user").that.is.an("object");
34+
expect(userApiInfo.user).to.deep.equal(userCommandInfo.result);
35+
});
36+
37+
it("should send a GET request with query parameters", async () => {
38+
const apiResponse = await runCommand(`api --method GET --path itwins --query displayName:${iTwin.displayName}`);
39+
expect(apiResponse.error).to.be.undefined;
40+
const apiResponseJSON = JSON.parse(apiResponse.stdout);
41+
42+
expect(apiResponseJSON).to.have.property("iTwins").that.is.an("array").with.lengthOf(1);
43+
expect(apiResponseJSON.iTwins[0]).to.have.property("displayName", iTwin.displayName);
44+
expect(apiResponseJSON.iTwins[0]).to.have.property("id", iTwin.id);
45+
});
46+
47+
it("should send a POST request with body parameters", async () => {
48+
const user = await runCommand<User>("user me");
49+
const apiResponse = await runCommand(`api --method POST --path users/getbyidlist --body ["${user.result?.id}"]`);
50+
expect(apiResponse.error).to.be.undefined;
51+
const apiResponseJSON = JSON.parse(apiResponse.stdout);
52+
53+
expect(apiResponseJSON).to.have.property("users").that.is.an("array").with.lengthOf(1);
54+
expect(apiResponseJSON.users[0]).to.have.property("id", user.result?.id);
55+
expect(apiResponseJSON.users[0]).to.have.property("email", user.result?.email);
56+
});
57+
58+
it("should update an iTwin with a PATCH request", async () => {
59+
const updatedName = `itwin-cli-test-updated-${new Date().toISOString()}`;
60+
// eslint-disable-next-line no-useless-escape
61+
const apiResponse = await runCommand(`api --method PATCH --path itwins/${iTwin.id} --body "{\"displayName\": \"${updatedName}\"}"`);
62+
expect(apiResponse.error).to.be.undefined;
63+
const apiResponseJSON = JSON.parse(apiResponse.stdout);
64+
65+
expect(apiResponseJSON).to.have.property("iTwin").that.is.an("object").and.not.undefined;
66+
expect(apiResponseJSON.iTwin).to.have.property("id", iTwin.id);
67+
expect(apiResponseJSON.iTwin).to.have.property("displayName", updatedName);
68+
});
69+
70+
it("should send a DELETE request to delete the iTwin", async () => {
71+
const apiResponse = await runCommand(`api --method DELETE --path itwins/${iTwin.id} --empty-response`);
72+
expect(apiResponse.error).to.be.undefined;
73+
const deleteResponse = JSON.parse(apiResponse.stdout);
74+
expect(deleteResponse).to.have.property("result").that.is.equal("success");
75+
});
76+
77+
it('should send the header to the API (iModel minimal)', async () => {
78+
const minimalApiResponse = await runCommand(`api --method GET --path imodels/?iTwinId=${iTwin.id} --header "Prefer: return=minimal"`);
79+
expect(minimalApiResponse.error).to.be.undefined;
80+
const minimalApiResponseJSON = JSON.parse(minimalApiResponse.stdout);
81+
82+
expect(minimalApiResponseJSON).to.have.property("iModels").that.is.an("array").with.lengthOf(1);
83+
expect(minimalApiResponseJSON.iModels[0]).to.have.property("id", iModel.id);
84+
expect(minimalApiResponseJSON.iModels[0]).to.have.property("displayName", iModel.displayName);
85+
expect(minimalApiResponseJSON.iModels[0]).to.not.have.property("createdDateTime");
86+
expect(minimalApiResponseJSON.iModels[0]).to.not.have.property("iTwinId");
87+
});
88+
89+
it('should send the header to the API (iModel representation)', async () => {
90+
const minimalApiResponse = await runCommand(`api --method GET --path imodels/?iTwinId=${iTwin.id} --header "Prefer: return=representation"`);
91+
expect(minimalApiResponse.error).to.be.undefined;
92+
const minimalApiResponseJSON = JSON.parse(minimalApiResponse.stdout);
93+
94+
expect(minimalApiResponseJSON).to.have.property("iModels").that.is.an("array").with.lengthOf(1);
95+
expect(minimalApiResponseJSON.iModels[0]).to.have.property("id", iModel.id);
96+
expect(minimalApiResponseJSON.iModels[0]).to.have.property("displayName", iModel.displayName);
97+
expect(minimalApiResponseJSON.iModels[0]).to.have.property("createdDateTime");
98+
expect(minimalApiResponseJSON.iModels[0]).to.have.property("iTwinId", iModel.iTwinId);
99+
});
100+
101+
it('should handle version header correctly (Echo V1)', async () => {
102+
const apiResponse = await runCommand(`api --method GET --path echo/context --version-header application/vnd.bentley.itwin-platform.v1+json`);
103+
expect(apiResponse.error).to.be.undefined;
104+
const apiResponseJSON = JSON.parse(apiResponse.stdout);
105+
106+
expect(apiResponseJSON).to.have.property("api").that.is.an("object").and.not.undefined;
107+
expect(apiResponseJSON.api).to.have.property("version").that.is.an("string").and.not.undefined;
108+
expect(apiResponseJSON.api.version).to.equal("v1");
109+
});
110+
});

src/commands/api/index.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,16 @@ export default class ApiRequest extends BaseCommand {
5757
}, {} as Record<string, string>) || {};
5858

5959
const query: Query[] | undefined = flags.query?.map((query) => {
60-
const [key, value] = query.split(":");
61-
return { key: key.trim(), value: value.trim() };
60+
const indexOfColon = query.indexOf(":");
61+
if (indexOfColon === -1) {
62+
this.error(`Invalid query format: ${query}. Expected format is 'key:value'.`);
63+
}
64+
65+
// Split the query string into key and value
66+
const key = query.slice(0, indexOfColon).trim();
67+
const value = query.slice(indexOfColon + 1).trim();
68+
69+
return { key, value };
6270
}) || undefined;
6371

6472
const client = await this.getITwinApiClient();

0 commit comments

Comments
 (0)