Skip to content

Commit a256894

Browse files
authored
Include context command functionality (#98)
1 parent 23032f0 commit a256894

14 files changed

Lines changed: 375 additions & 11 deletions

File tree

docs/_sidebar.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
* [info](/docs/auth/info.md)
1212
* [login](/docs/auth/login.md)
1313
* [logout](/docs/auth/logout.md)
14+
* [context](/docs/context/overview.md)
15+
* [set](/docs/context/set.md)
16+
* [clear](/docs/context/clear.md)
17+
* [info](/docs/context/info.md)
1418
* [api](/docs/api.md)
1519
* [itwin](/docs/itwin/overview.md)
1620
* [create](/docs/itwin/create.md)

docs/context/clear.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# itp context clear
2+
3+
Clear the cached context.
4+
5+
## Examples
6+
7+
```bash
8+
# Example 1: Clear the cached context
9+
itp context clear
10+
```

docs/context/info.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# itp context info
2+
3+
Display the cached context.
4+
5+
## Examples
6+
7+
```bash
8+
# Example 1: Display the cached context
9+
itp context info
10+
```

docs/context/overview.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# itp context
2+
3+
Work with iTwin CLI context.
4+
5+
## Available Commands
6+
7+
- [itp context set](set.md)
8+
- [itp context info](info.md)
9+
- [itp context clear](clear.md)

docs/context/set.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# itp context set
2+
3+
Set a new cached context.
4+
5+
## Options
6+
7+
- **`-m, --imodel-id`**
8+
The ID of the iModel to create a context for.
9+
**Type:** `string` **Required:** No
10+
11+
- **`-i, --itwin-id`**
12+
The ID of the iTwin to create a context for.
13+
**Type:** `string` **Required:** No
14+
15+
## Examples
16+
17+
```bash
18+
# Example 1: Set a new cached context using an iTwin ID
19+
itp context set --itwin-id 12345
20+
21+
# Example 2: Set a new cached context using an iModel ID
22+
itp context set --imodel-id 67890
23+
24+
# Example 3: Error when neither --itwin-id nor --imodel-id is provided
25+
itp context set
26+
```
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
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 { loginToCli } from "../utils/helpers";
7+
8+
describe('Context Integration Tests', () => {
9+
let iTwin: ITwin;
10+
let iModel: IModel;
11+
let anotherITwin: ITwin;
12+
13+
before(async () => {
14+
await loginToCli();
15+
const name = `IntegrationTestITwin_${new Date().toISOString()}`;
16+
const iTwinResult = await runCommand<ITwin>(`itwin create --name "${name}" --class Thing --sub-class Asset`);
17+
expect(iTwinResult.error).to.be.undefined;
18+
expect(iTwinResult.result).to.not.be.undefined;
19+
iTwin = iTwinResult.result as ITwin;
20+
21+
const iModelName = `IntegrationTestIModel_${new Date().toISOString()}`;
22+
const iModelResult = await runCommand(`imodel create --name "${iModelName}" --itwin-id ${iTwin.id}`);
23+
expect(iModelResult.error).to.be.undefined;
24+
expect(iModelResult.result).to.not.be.undefined;
25+
iModel = iModelResult.result as IModel;
26+
27+
const anotherITwinName = `AnotherITwin_${new Date().toISOString()}`;
28+
const anotherITwinResult = await runCommand<ITwin>(`itwin create --name "${anotherITwinName}" --class Thing --sub-class Asset`);
29+
expect(anotherITwinResult.error).to.be.undefined;
30+
expect(anotherITwinResult.result).to.not.be.undefined;
31+
anotherITwin = anotherITwinResult.result as ITwin;
32+
});
33+
34+
after(async () => {
35+
await runCommand(`itwin delete --id ${iTwin.id}`);
36+
await runCommand(`itwin delete --id ${anotherITwin.id}`);
37+
});
38+
39+
beforeEach(async () => {
40+
const output = await runCommand('context clear');
41+
expect(output.error).to.be.undefined;
42+
});
43+
44+
it('should clear the context', async () => {
45+
const output = await runCommand('context clear');
46+
expect(output.error).to.be.undefined;
47+
expect(output.stdout).to.contain('Context cleared.');
48+
49+
const outputInfo = await runCommand('context info');
50+
expect(outputInfo.error).to.be.undefined;
51+
expect(outputInfo.result).to.be.undefined;
52+
});
53+
54+
it('should set the context', async () => {
55+
const output = await runCommand(`context set --itwin-id ${iTwin.id} --imodel-id ${iModel.id}`);
56+
expect(output.error).to.be.undefined;
57+
expect(output.result).to.deep.equal({ iModelId: iModel.id, iTwinId: iTwin.id });
58+
});
59+
60+
it('should fail to set context with invalid iTwin ID', async () => {
61+
const invalidITwinId = "invalid-id";
62+
const output = await runCommand(`context set --itwin-id ${invalidITwinId}`);
63+
expect(output.error).to.not.be.undefined;
64+
expect(output.error?.message).to.contain('Requested iTwin is not available.');
65+
});
66+
67+
it('should fail to set context with mismatched iModel and iTwin IDs', async () => {
68+
const output = await runCommand(`context set --itwin-id ${anotherITwin.id} --imodel-id ${iModel.id}`);
69+
expect(output.error).to.not.be.undefined;
70+
expect(output.error?.message).to.contain(`The iModel ID ${iModel.id} does not belong to the specified iTwin ID ${anotherITwin.id}.`);
71+
});
72+
73+
it('should fail to set context without iTwin or iModel ID', async () => {
74+
const output = await runCommand('context set');
75+
expect(output.error).to.not.be.undefined;
76+
expect(output.error?.message).to.contain('Either --itwin-id or --imodel-id must be provided.');
77+
});
78+
79+
it('should display the current context', async () => {
80+
await runCommand(`context set --itwin-id ${iTwin.id} --imodel-id ${iModel.id}`);
81+
const output = await runCommand('context info');
82+
expect(output.error).to.be.undefined;
83+
expect(output.result).to.deep.equal({ iModelId: iModel.id, iTwinId: iTwin.id });
84+
});
85+
86+
it('should display undefined context after clearing', async () => {
87+
await runCommand('context clear');
88+
const output = await runCommand('context info');
89+
expect(output.error).to.be.undefined;
90+
expect(output.result).to.be.undefined;
91+
});
92+
93+
it('should clear context multiple times without error', async () => {
94+
await runCommand('context clear');
95+
const output = await runCommand('context clear');
96+
expect(output.error).to.be.undefined;
97+
expect(output.stdout).to.contain('Context cleared.');
98+
});
99+
100+
it('should set the context with only an iTwin ID', async () => {
101+
const output = await runCommand(`context set --itwin-id ${iTwin.id}`);
102+
expect(output.error).to.be.undefined;
103+
expect(output.result).to.deep.equal({ iModelId: undefined, iTwinId: iTwin.id });
104+
});
105+
106+
it('should set the context with only an iModel ID and resolve the correct iTwin ID', async () => {
107+
const output = await runCommand(`context set --imodel-id ${iModel.id}`);
108+
expect(output.error).to.be.undefined;
109+
expect(output.result).to.deep.equal({ iModelId: iModel.id, iTwinId: iTwin.id });
110+
});
111+
112+
it('should fail to set context with only an iModel ID if the iModel does not exist', async () => {
113+
const invalidIModelId = "invalid-id";
114+
const output = await runCommand(`context set --imodel-id ${invalidIModelId}`);
115+
expect(output.error).to.not.be.undefined;
116+
expect(output.error?.message).to.contain('Requested iModel is not available.');
117+
});
118+
});

integration-tests/formating/formatting.test.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,25 @@ import { expect } from "chai";
44
describe('Command formatting tests', async () => {
55
let allCommands : CommandWithFlags[] = [];
66

7+
const commandToExcludeFromTests = [
8+
"help",
9+
"plugins"
10+
];
11+
712
before(async () => {
813
const config = await Config.load({
914
devPlugins: false,
1015
root: process.cwd(),
1116
userPlugins: false,
1217
});
1318

14-
allCommands = config.commands.filter(command => !command.id.startsWith("plugins") && !command.id.startsWith("help") && !command.hidden)
15-
.map((command) =>
16-
({
17-
cmd: command,
18-
flags: Object.entries(command.flags),
19-
}));
19+
allCommands = config.commands.filter(command =>
20+
!commandToExcludeFromTests.some(excluded => command.id.startsWith(excluded)) && !command.hidden
21+
).map((command) =>
22+
({
23+
cmd: command,
24+
flags: Object.entries(command.flags),
25+
}));
2026
});
2127

2228
it('Should ensure all commands have a description', async () => {
@@ -39,7 +45,8 @@ describe('Command formatting tests', async () => {
3945
});
4046

4147
it('Should ensure all itwin-id flags have env properties', async () => {
42-
for (const command of allCommands) {
48+
// Exclude context:set command from this test as it has a special case for itwin-id flag
49+
for (const command of allCommands.filter(cmd => cmd.cmd.id !== "context:set")) {
4350
const iTwinIdFlag = command.flags.find(([name, _]) => name === "itwin-id");
4451
if (iTwinIdFlag) {
4552
expect(iTwinIdFlag[1].env, `Flag 'itwin-id' in command '${command.cmd.id}' is missing the 'env' property`).to.be.a('string').and.be.equals("ITP_ITWIN_ID");
@@ -48,7 +55,8 @@ describe('Command formatting tests', async () => {
4855
});
4956

5057
it('Should ensure all imodel-id flags have env properties', async () => {
51-
for (const command of allCommands) {
58+
// Exclude context:set command from this test as it has a special case for imodel-id flag
59+
for (const command of allCommands.filter(cmd => cmd.cmd.id !== "context:set")) {
5260
const iTwinIdFlag = command.flags.find(([name, _]) => name === "imodel-id");
5361
if (iTwinIdFlag) {
5462
expect(iTwinIdFlag[1].env, `Flag 'imodel-id' in command '${command.cmd.id}' is missing the 'env' property`).to.be.a('string').and.be.equals("ITP_IMODEL_ID");

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"@itwin/node-cli-authorization": "^2.0.3",
1414
"@itwin/object-storage-core": "^2.2.5",
1515
"@itwin/service-authorization": "^1.2.2",
16-
"@oclif/core": "^4.2.8",
16+
"@oclif/core": "^4.2.10",
1717
"@oclif/plugin-help": "^6",
1818
"@oclif/plugin-plugins": "^5",
1919
"@opentelemetry/api": "^1.9.0",

src/commands/context/clear.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import BaseCommand from "../../extensions/base-command.js";
2+
3+
export default class ClearContext extends BaseCommand {
4+
static description = "Clear the cached context.";
5+
6+
static examples = [
7+
{
8+
command: `<%= config.bin %> <%= command.id %>`,
9+
description: 'Example 1: Clear the cached context'
10+
}
11+
];
12+
13+
async run() {
14+
this.clearContext();
15+
return this.logAndReturnResult({ result: "Context cleared." });
16+
}
17+
}

0 commit comments

Comments
 (0)