Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,40 @@
"--itwin-id",
"<itwin-id>"
]
},
{
"name": "Debug All Tests",
"skipFiles": [
"<node_internals>/**"
],
"type": "node",
"request": "launch",
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/mocha",
"args": [
"--forbid-only",
"--timeout",
"999999",
"integration-tests/**/api.test.ts"
],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
},
{
"name": "Debug Active Test",
"skipFiles": [
"<node_internals>/**"
],
"type": "node",
"request": "launch",
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/mocha",
"args": [
"--forbid-only",
"--timeout",
"999999",
"${file}"
],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
}
]
}
3 changes: 3 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ itp api --method DELETE --path itwins/favorites/dc914a84-e0c9-40e2-9d14-faf5ed84
# Example 4: Sending a post request
itp api --method POST --path itwins/exports --body '{"outputFormat": "JsonGZip"}'

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

```

## APIs Docs Reference
Expand Down
100 changes: 100 additions & 0 deletions integration-tests/api.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { IModel } from "@itwin/imodels-client-management";
import { ITwin } from "@itwin/itwins-client";
import { runCommand } from "@oclif/test";
import { expect } from "chai";

import { User } from "../src/services/user-client/models/user.js";

describe("API Integration Tests", () => {
let iTwin: ITwin;
let iModel: IModel;

before(async () => {
const date = new Date();
const iTwinRespone = await runCommand<ITwin>(`itwin create --class Thing --sub-class Asset --name itwin-cli-test-${date.toISOString()}`);
expect(iTwinRespone.error).to.be.undefined;
iTwin = iTwinRespone.result!;
const iModelResponse = await runCommand<IModel>(`imodel create --name imodel-cli-test-${date.toISOString()} --itwin-id ${iTwin.id}`);
expect(iModelResponse.error).to.be.undefined;
iModel = iModelResponse.result!;
});

after(async () => {
await runCommand(`itwin delete -i ${iTwin.id}`);
});

it("should send a GET request and get user me info", async () => {
const apiResponse = await runCommand("api --method GET --path users/me");
expect(apiResponse.error).to.be.undefined;
const userApiInfo = JSON.parse(apiResponse.stdout);

const userCommandInfo = await runCommand<User>("user me");

expect(userApiInfo).to.have.property("user").that.is.an("object");
expect(userApiInfo.user).to.deep.equal(userCommandInfo.result);
});

it("should send a GET request with query parameters", async () => {
const apiResponse = await runCommand(`api --method GET --path itwins --query displayName:${iTwin.displayName}`);
expect(apiResponse.error).to.be.undefined;
const apiResponseJSON = JSON.parse(apiResponse.stdout);

expect(apiResponseJSON).to.have.property("iTwins").that.is.an("array").with.lengthOf(1);
expect(apiResponseJSON.iTwins[0]).to.have.property("displayName", iTwin.displayName);
expect(apiResponseJSON.iTwins[0]).to.have.property("id", iTwin.id);
});

it("should send a POST request with body parameters", async () => {
const user = await runCommand<User>("user me");
const apiResponse = await runCommand(`api --method POST --path users/getbyidlist --body ["${user.result?.id}"]`);
expect(apiResponse.error).to.be.undefined;
const apiResponseJSON = JSON.parse(apiResponse.stdout);

expect(apiResponseJSON).to.have.property("users").that.is.an("array").with.lengthOf(1);
expect(apiResponseJSON.users[0]).to.have.property("id", user.result?.id);
expect(apiResponseJSON.users[0]).to.have.property("email", user.result?.email);
});

it("should update an iTwin with a PATCH request", async () => {
const updatedName = `itwin-cli-test-updated-${new Date().toISOString()}`;
// eslint-disable-next-line no-useless-escape
const apiResponse = await runCommand(`api --method PATCH --path itwins/${iTwin.id} --body "{\"displayName\": \"${updatedName}\"}"`);
expect(apiResponse.error).to.be.undefined;
const apiResponseJSON = JSON.parse(apiResponse.stdout);

expect(apiResponseJSON).to.have.property("iTwin").that.is.an("object").and.not.undefined;
expect(apiResponseJSON.iTwin).to.have.property("id", iTwin.id);
expect(apiResponseJSON.iTwin).to.have.property("displayName", updatedName);
});

it("should send a DELETE request to delete the iTwin", async () => {
const apiResponse = await runCommand(`api --method DELETE --path itwins/${iTwin.id} --empty-response`);
expect(apiResponse.error).to.be.undefined;
const deleteResponse = JSON.parse(apiResponse.stdout);
expect(deleteResponse).to.have.property("result").that.is.equal("success");
});

it('should send the header to the API (iModel minimal)', async () => {
const minimalApiResponse = await runCommand(`api --method GET --path imodels/?iTwinId=${iTwin.id} --header "Prefer: return=minimal"`);
expect(minimalApiResponse.error).to.be.undefined;
const minimalApiResponseJSON = JSON.parse(minimalApiResponse.stdout);

expect(minimalApiResponseJSON).to.have.property("iModels").that.is.an("array").with.lengthOf(1);
expect(minimalApiResponseJSON.iModels[0]).to.have.property("id", iModel.id);
expect(minimalApiResponseJSON.iModels[0]).to.have.property("displayName", iModel.displayName);
expect(minimalApiResponseJSON.iModels[0]).to.not.have.property("createdDateTime");
expect(minimalApiResponseJSON.iModels[0]).to.not.have.property("iTwinId");
});

it('should send the header to the API (iModel representation)', async () => {
const minimalApiResponse = await runCommand(`api --method GET --path imodels/?iTwinId=${iTwin.id} --header "Prefer: return=representation"`);
expect(minimalApiResponse.error).to.be.undefined;
const minimalApiResponseJSON = JSON.parse(minimalApiResponse.stdout);

expect(minimalApiResponseJSON).to.have.property("iModels").that.is.an("array").with.lengthOf(1);
expect(minimalApiResponseJSON.iModels[0]).to.have.property("id", iModel.id);
expect(minimalApiResponseJSON.iModels[0]).to.have.property("displayName", iModel.displayName);
expect(minimalApiResponseJSON.iModels[0]).to.have.property("createdDateTime");
expect(minimalApiResponseJSON.iModels[0]).to.have.property("iTwinId", iModel.iTwinId);
});
});
12 changes: 10 additions & 2 deletions src/commands/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,16 @@ export default class ApiRequest extends BaseCommand {
}, {} as Record<string, string>) || {};

const query: Query[] | undefined = flags.query?.map((query) => {
const [key, value] = query.split(":");
return { key: key.trim(), value: value.trim() };
const indexOfColon = query.indexOf(":");
if (indexOfColon === -1) {
this.error(`Invalid query format: ${query}. Expected format is 'key:value'.`);
}

// Split the query string into key and value
const key = query.slice(0, indexOfColon).trim();
const value = query.slice(indexOfColon + 1).trim();

return { key, value };
}) || undefined;

const client = await this.getITwinApiClient();
Expand Down