gemiterm 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env +2 -0
- package/.github/workflows/manual-release.yml +34 -0
- package/.github/workflows/release.yml +198 -0
- package/.github/workflows/test.yml +29 -0
- package/.opencode/commands/opsx-apply.md +152 -0
- package/.opencode/commands/opsx-archive.md +157 -0
- package/.opencode/commands/opsx-explore.md +169 -0
- package/.opencode/commands/opsx-propose.md +104 -0
- package/.opencode/commands/opsx-sync.md +140 -0
- package/.opencode/commands/search-intelligently.md +9 -0
- package/.opencode/opencode.json +20 -0
- package/.opencode/skills/intellisearch/.version +1 -0
- package/.opencode/skills/intellisearch/SKILL.md +84 -0
- package/.opencode/skills/intellisearch/references/brave-search.md +67 -0
- package/.opencode/skills/intellisearch/references/ddg-search.md +79 -0
- package/.opencode/skills/intellisearch/references/deepwiki-tools.md +246 -0
- package/.opencode/skills/intellisearch/references/examples.md +97 -0
- package/.opencode/skills/intellisearch/references/gh-cli.md +143 -0
- package/.opencode/skills/intellisearch/references/google-search.md +40 -0
- package/.opencode/skills/intellisearch/references/search-workflow.md +143 -0
- package/.opencode/skills/intellisearch/references/workflow.md +115 -0
- package/.opencode/skills/openspec-apply-change/SKILL.md +159 -0
- package/.opencode/skills/openspec-archive-change/SKILL.md +117 -0
- package/.opencode/skills/openspec-explore/SKILL.md +287 -0
- package/.opencode/skills/openspec-propose/SKILL.md +111 -0
- package/.opencode/skills/openspec-sync-specs/SKILL.md +147 -0
- package/.serena/project.yml +133 -0
- package/AGENTS.md +136 -0
- package/CHANGELOG.md +3 -0
- package/README.md +245 -0
- package/bun.lock +105 -0
- package/docs/INSTALL.md +185 -0
- package/docs/PLAYWRIGHT_CLI_API.md +306 -0
- package/docs/python-release-for-reference.md +175 -0
- package/examples/sample_json_export.json +32 -0
- package/examples/sample_markdown_export.md +221 -0
- package/examples/usage_examples.sh +222 -0
- package/install.ps1 +264 -0
- package/install.sh +275 -0
- package/openspec/MAESTRO_MIGRATION.md +180 -0
- package/openspec/changes/archive/2026-06-08-command-spec-conformance/.openspec.yaml +2 -0
- package/openspec/changes/archive/2026-06-08-command-spec-conformance/design.md +194 -0
- package/openspec/changes/archive/2026-06-08-command-spec-conformance/proposal.md +52 -0
- package/openspec/changes/archive/2026-06-08-command-spec-conformance/specs/multi-profile-conversations/spec.md +133 -0
- package/openspec/changes/archive/2026-06-08-command-spec-conformance/tasks.md +95 -0
- package/openspec/changes/archive/2026-06-09-refactor-interactive-prompt-loop/.openspec.yaml +2 -0
- package/openspec/changes/archive/2026-06-09-refactor-interactive-prompt-loop/design.md +28 -0
- package/openspec/changes/archive/2026-06-09-refactor-interactive-prompt-loop/proposal.md +22 -0
- package/openspec/changes/archive/2026-06-09-refactor-interactive-prompt-loop/specs/interactive-prompt-loop/spec.md +35 -0
- package/openspec/changes/archive/2026-06-09-refactor-interactive-prompt-loop/tasks.md +28 -0
- package/openspec/changes/archive/2026-06-09-replace-gemini-api-placeholder-with-gemini-reverse/.openspec.yaml +2 -0
- package/openspec/changes/archive/2026-06-09-replace-gemini-api-placeholder-with-gemini-reverse/design.md +323 -0
- package/openspec/changes/archive/2026-06-09-replace-gemini-api-placeholder-with-gemini-reverse/proposal.md +122 -0
- package/openspec/changes/archive/2026-06-09-replace-gemini-api-placeholder-with-gemini-reverse/specs/no-capability-changes/spec.md +29 -0
- package/openspec/changes/archive/2026-06-09-replace-gemini-api-placeholder-with-gemini-reverse/tasks.md +172 -0
- package/openspec/changes/archive/2026-06-09-v2-install-migration/.openspec.yaml +2 -0
- package/openspec/changes/archive/2026-06-09-v2-install-migration/design.md +241 -0
- package/openspec/changes/archive/2026-06-09-v2-install-migration/proposal.md +41 -0
- package/openspec/changes/archive/2026-06-09-v2-install-migration/specs/v2-installer/spec.md +205 -0
- package/openspec/changes/archive/2026-06-09-v2-install-migration/tasks.md +97 -0
- package/openspec/changes/commander-cli-parser/.openspec.yaml +2 -0
- package/openspec/changes/commander-cli-parser/design.md +107 -0
- package/openspec/changes/commander-cli-parser/proposal.md +31 -0
- package/openspec/changes/commander-cli-parser/specs/cli/spec.md +48 -0
- package/openspec/changes/commander-cli-parser/tasks.md +33 -0
- package/openspec/changes/cross-platform-build-and-ci/.openspec.yaml +2 -0
- package/openspec/changes/cross-platform-build-and-ci/design.md +213 -0
- package/openspec/changes/cross-platform-build-and-ci/proposal.md +50 -0
- package/openspec/changes/cross-platform-build-and-ci/specs/path-and-file-mediation/spec.md +335 -0
- package/openspec/changes/cross-platform-build-and-ci/specs/release-pipeline/spec.md +95 -0
- package/openspec/changes/cross-platform-build-and-ci/tasks.md +164 -0
- package/openspec/config.yaml +20 -0
- package/openspec/specs/auth/spec.md +394 -0
- package/openspec/specs/cli/spec.md +108 -0
- package/openspec/specs/commands/spec.md +409 -0
- package/openspec/specs/configuration/spec.md +130 -0
- package/openspec/specs/conversations/spec.md +139 -0
- package/openspec/specs/domain-model/spec.md +128 -0
- package/openspec/specs/formatters/spec.md +158 -0
- package/openspec/specs/install-browser/spec.md +146 -0
- package/openspec/specs/interactive-prompt-loop/spec.md +41 -0
- package/openspec/specs/logger/spec.md +71 -0
- package/openspec/specs/mediator/spec.md +72 -0
- package/openspec/specs/multi-profile-conversations/spec.md +145 -0
- package/openspec/specs/path-utils/spec.md +101 -0
- package/openspec/specs/profiles/spec.md +94 -0
- package/openspec/specs/storage/spec.md +198 -0
- package/openspec/specs/testing/spec.md +237 -0
- package/openspec/specs/v2-installer/spec.md +205 -0
- package/openspec/specs/validators/spec.md +65 -0
- package/package.json +45 -0
- package/scripts/build.ts +88 -0
- package/scripts/clean-build.ps1 +4 -0
- package/scripts/clean-build.sh +5 -0
- package/scripts/install-browser.ps1 +5 -0
- package/scripts/install-browser.sh +6 -0
- package/scripts/lint-path-mediation.ps1 +65 -0
- package/scripts/lint-path-mediation.sh +40 -0
- package/src/cli/.gitkeep +0 -0
- package/src/cli/command-registry.ts +62 -0
- package/src/cli/commands/auth-command.ts +179 -0
- package/src/cli/commands/continue-command.ts +158 -0
- package/src/cli/commands/delete-command.ts +163 -0
- package/src/cli/commands/export-all-command.ts +243 -0
- package/src/cli/commands/export-command.ts +156 -0
- package/src/cli/commands/fetch-command.ts +176 -0
- package/src/cli/commands/help.ts +45 -0
- package/src/cli/commands/install-browser-command.ts +33 -0
- package/src/cli/commands/list-command.ts +226 -0
- package/src/cli/commands/new-command.ts +147 -0
- package/src/cli/commands/profile-command.ts +226 -0
- package/src/cli/commands/status-command.ts +59 -0
- package/src/cli/index.ts +213 -0
- package/src/cli/utils/interactive-prompt.ts +64 -0
- package/src/core/.gitkeep +0 -0
- package/src/core/command-handlers.ts +240 -0
- package/src/core/errors.ts +41 -0
- package/src/core/mediator.ts +58 -0
- package/src/core/query-handlers.ts +163 -0
- package/src/core/types.ts +43 -0
- package/src/infrastructure/.gitkeep +0 -0
- package/src/infrastructure/cli-table.ts +45 -0
- package/src/infrastructure/config.ts +52 -0
- package/src/infrastructure/formatters.ts +130 -0
- package/src/infrastructure/io.ts +161 -0
- package/src/infrastructure/logger.ts +41 -0
- package/src/infrastructure/path-utils.ts +157 -0
- package/src/infrastructure/storage.ts +209 -0
- package/src/infrastructure/validators.ts +28 -0
- package/src/services/.gitkeep +0 -0
- package/src/services/auth-service.ts +135 -0
- package/src/services/cookie-monitor.ts +159 -0
- package/src/services/cookie-storage-service.ts +74 -0
- package/src/services/gemini-client-wrapper.ts +239 -0
- package/src/services/install-browser-service.ts +205 -0
- package/src/services/playwright-cli-driver.ts +269 -0
- package/src/services/profile-auth-manager.ts +65 -0
- package/src/services/profile-service.ts +121 -0
- package/tests/cli/auth-command.test.ts +220 -0
- package/tests/cli/command-registry.test.ts +67 -0
- package/tests/cli/continue-command.test.ts +100 -0
- package/tests/cli/delete-command.test.ts +128 -0
- package/tests/cli/export-all-command.test.ts +375 -0
- package/tests/cli/install-browser-command.test.ts +48 -0
- package/tests/cli/list-command.test.ts +179 -0
- package/tests/cli/new-command.test.ts +139 -0
- package/tests/cli/status-command.test.ts +106 -0
- package/tests/core/query-handlers.test.ts +187 -0
- package/tests/fixtures/auth-fixtures.ts +98 -0
- package/tests/fixtures/chat-fixtures.ts +87 -0
- package/tests/infrastructure/cli-table.test.ts +107 -0
- package/tests/infrastructure/formatters.test.ts +202 -0
- package/tests/infrastructure/io.test.ts +197 -0
- package/tests/infrastructure/logger.test.ts +74 -0
- package/tests/infrastructure/storage.test.ts +335 -0
- package/tests/infrastructure/validators.test.ts +67 -0
- package/tests/integration/commands/auth.test.ts +186 -0
- package/tests/integration/commands/continue.test.ts +130 -0
- package/tests/integration/commands/delete.test.ts +140 -0
- package/tests/integration/commands/export.test.ts +266 -0
- package/tests/integration/commands/fetch.test.ts +238 -0
- package/tests/integration/commands/list.test.ts +281 -0
- package/tests/integration/commands/profile.test.ts +258 -0
- package/tests/integration/commands/status.test.ts +223 -0
- package/tests/integration/installer-script-shape.test.ts +67 -0
- package/tests/parity/compare-outputs.ts +359 -0
- package/tests/parity/test-commands-parity.ps1 +82 -0
- package/tests/parity/test-commands-parity.sh +61 -0
- package/tests/services/auth-service.test.ts +315 -0
- package/tests/services/cookie-monitor.test.ts +229 -0
- package/tests/services/cookie-storage-service.test.ts +230 -0
- package/tests/services/gemini-client-wrapper.test.ts +688 -0
- package/tests/services/install-browser-service.test.ts +83 -0
- package/tests/services/playwright-cli-driver.test.ts +468 -0
- package/tests/services/profile-auth-manager.test.ts +334 -0
- package/tests/services/profile-service.test.ts +224 -0
- package/tests/setup.ts +95 -0
- package/tests/smoke/smoke.test.ts +52 -0
- package/tests/unit/config.test.ts +159 -0
- package/tests/unit/errors.test.ts +119 -0
- package/tests/unit/formatters.test.ts +316 -0
- package/tests/unit/mediator.test.ts +152 -0
- package/tests/unit/path-utils.test.ts +289 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, mock, spyOn } from "bun:test";
|
|
2
|
+
import { InstallBrowserCommand } from "../../src/cli/commands/install-browser-command.ts";
|
|
3
|
+
import { InstallBrowserService, InstallBrowserError } from "../../src/services/install-browser-service.ts";
|
|
4
|
+
|
|
5
|
+
describe("InstallBrowserCommand", () => {
|
|
6
|
+
let command: InstallBrowserCommand;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
command = new InstallBrowserCommand();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test("has correct name and description", () => {
|
|
13
|
+
expect(command.name).toBe("install-browser");
|
|
14
|
+
expect(command.description).toContain("Chromium");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("implements CliCommand interface", () => {
|
|
18
|
+
expect(typeof command.execute).toBe("function");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("exits with code 1 on InstallBrowserError", async () => {
|
|
22
|
+
const exitSpy = spyOn(process, "exit").mockImplementation(() => {
|
|
23
|
+
throw new Error("process.exit");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const service = new InstallBrowserService();
|
|
27
|
+
spyOn(service, "install").mockImplementation(async () => {
|
|
28
|
+
throw new InstallBrowserError("install failed");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const spy = spyOn(InstallBrowserService.prototype, "install").mockImplementation(async () => {
|
|
32
|
+
throw new InstallBrowserError("install failed");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
await expect(command.execute([], { verbose: false })).rejects.toThrow("process.exit");
|
|
36
|
+
|
|
37
|
+
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
38
|
+
spy.mockRestore();
|
|
39
|
+
exitSpy.mockRestore();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("succeeds when install completes", async () => {
|
|
43
|
+
const spy = spyOn(InstallBrowserService.prototype, "install").mockResolvedValue(undefined);
|
|
44
|
+
|
|
45
|
+
await expect(command.execute([], { verbose: false })).resolves.toBeUndefined();
|
|
46
|
+
spy.mockRestore();
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { describe, test, expect, mock, beforeEach, afterEach, spyOn } from "bun:test";
|
|
2
|
+
import { ListCommand } from "../../src/cli/commands/list-command.ts";
|
|
3
|
+
import { Mediator } from "../../src/core/mediator.ts";
|
|
4
|
+
import type { CliCommandContext } from "../../src/cli/command-registry.ts";
|
|
5
|
+
import { QUERY_TYPES } from "../../src/core/query-handlers.ts";
|
|
6
|
+
import type { ListChatsQueryResult } from "../../src/core/query-handlers.ts";
|
|
7
|
+
|
|
8
|
+
const SAMPLE_CHATS = [
|
|
9
|
+
{ id: "abc123", title: "Python tips", isPinned: true, timestamp: 1717000000000 },
|
|
10
|
+
{ id: "def456", title: "Bun setup", isPinned: false, timestamp: 1717100000000 },
|
|
11
|
+
{ id: "ghi789", title: "Alpha test", isPinned: false, timestamp: 1716900000000 },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
describe("ListCommand", () => {
|
|
15
|
+
let command: ListCommand;
|
|
16
|
+
let mediator: Mediator;
|
|
17
|
+
let context: CliCommandContext;
|
|
18
|
+
let logSpy: ReturnType<typeof spyOn>;
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
command = new ListCommand();
|
|
22
|
+
mediator = new Mediator();
|
|
23
|
+
context = { verbose: false, mediator };
|
|
24
|
+
logSpy = spyOn(console, "log").mockImplementation(() => {});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
mock.restore();
|
|
29
|
+
logSpy.mockRestore();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("has correct name and description", () => {
|
|
33
|
+
expect(command.name).toBe("list");
|
|
34
|
+
expect(command.description).toBe("List conversations");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("shows help with --help flag", async () => {
|
|
38
|
+
await command.execute(["--help"], context);
|
|
39
|
+
const output = logSpy.mock.calls.map((c) => c[0]).join("\n");
|
|
40
|
+
expect(output).toContain("Usage: gemiterm list");
|
|
41
|
+
expect(output).toContain("--limit");
|
|
42
|
+
expect(output).toContain("--format");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("sends list-chats query and displays results", async () => {
|
|
46
|
+
const mockHandler = {
|
|
47
|
+
queryType: QUERY_TYPES.LIST_CHATS,
|
|
48
|
+
handle: mock(async () => ({ chats: SAMPLE_CHATS })),
|
|
49
|
+
};
|
|
50
|
+
mediator.registerQueryHandler(mockHandler as any);
|
|
51
|
+
|
|
52
|
+
await command.execute([], context);
|
|
53
|
+
|
|
54
|
+
const output = logSpy.mock.calls.map((c) => c[0]).join("\n");
|
|
55
|
+
expect(output).toContain("abc123");
|
|
56
|
+
expect(output).toContain("Python tips");
|
|
57
|
+
expect(output).toContain("Total: 3 conversations");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("applies limit", async () => {
|
|
61
|
+
const mockHandler = {
|
|
62
|
+
queryType: QUERY_TYPES.LIST_CHATS,
|
|
63
|
+
handle: mock(async () => ({ chats: SAMPLE_CHATS })),
|
|
64
|
+
};
|
|
65
|
+
mediator.registerQueryHandler(mockHandler as any);
|
|
66
|
+
|
|
67
|
+
await command.execute(["--limit", "1"], context);
|
|
68
|
+
|
|
69
|
+
const output = logSpy.mock.calls.map((c) => c[0]).join("\n");
|
|
70
|
+
expect(output).toContain("Total: 1 conversation");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("applies search filter", async () => {
|
|
74
|
+
const mockHandler = {
|
|
75
|
+
queryType: QUERY_TYPES.LIST_CHATS,
|
|
76
|
+
handle: mock(async () => {
|
|
77
|
+
const filtered = SAMPLE_CHATS.filter((c) => c.title.toLowerCase().includes("bun"));
|
|
78
|
+
return { chats: filtered };
|
|
79
|
+
}),
|
|
80
|
+
};
|
|
81
|
+
mediator.registerQueryHandler(mockHandler as any);
|
|
82
|
+
|
|
83
|
+
await command.execute(["--search", "Bun"], context);
|
|
84
|
+
|
|
85
|
+
expect(mockHandler.handle).toHaveBeenCalledWith(
|
|
86
|
+
expect.objectContaining({
|
|
87
|
+
payload: expect.objectContaining({ search: "Bun" }),
|
|
88
|
+
}),
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("applies --all flag", async () => {
|
|
93
|
+
const mockHandler = {
|
|
94
|
+
queryType: QUERY_TYPES.LIST_CHATS,
|
|
95
|
+
handle: mock(async () => ({ chats: SAMPLE_CHATS })),
|
|
96
|
+
};
|
|
97
|
+
mediator.registerQueryHandler(mockHandler as any);
|
|
98
|
+
|
|
99
|
+
await command.execute(["--all"], context);
|
|
100
|
+
|
|
101
|
+
const output = logSpy.mock.calls.map((c) => c[0]).join("\n");
|
|
102
|
+
expect(output).toContain("Total: 3 conversations");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("applies sort by alpha", async () => {
|
|
106
|
+
const mockHandler = {
|
|
107
|
+
queryType: QUERY_TYPES.LIST_CHATS,
|
|
108
|
+
handle: mock(async () => ({ chats: SAMPLE_CHATS })),
|
|
109
|
+
};
|
|
110
|
+
mediator.registerQueryHandler(mockHandler as any);
|
|
111
|
+
|
|
112
|
+
await command.execute(["--sort", "alpha"], context);
|
|
113
|
+
|
|
114
|
+
const output = logSpy.mock.calls.map((c) => c[0]).join("\n");
|
|
115
|
+
const alphaIdx = output.indexOf("Alpha test");
|
|
116
|
+
const bunIdx = output.indexOf("Bun setup");
|
|
117
|
+
const pythonIdx = output.indexOf("Python tips");
|
|
118
|
+
expect(alphaIdx).toBeLessThan(bunIdx);
|
|
119
|
+
expect(bunIdx).toBeLessThan(pythonIdx);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("outputs JSON format", async () => {
|
|
123
|
+
const mockHandler = {
|
|
124
|
+
queryType: QUERY_TYPES.LIST_CHATS,
|
|
125
|
+
handle: mock(async () => ({ chats: SAMPLE_CHATS })),
|
|
126
|
+
};
|
|
127
|
+
mediator.registerQueryHandler(mockHandler as any);
|
|
128
|
+
|
|
129
|
+
await command.execute(["--format", "json"], context);
|
|
130
|
+
|
|
131
|
+
const output = logSpy.mock.calls.map((c) => c[0]).join("\n");
|
|
132
|
+
const parsed = JSON.parse(output);
|
|
133
|
+
expect(parsed).toHaveProperty("chats");
|
|
134
|
+
expect(parsed.chats).toHaveLength(3);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("shows no conversations message when empty", async () => {
|
|
138
|
+
const mockHandler = {
|
|
139
|
+
queryType: QUERY_TYPES.LIST_CHATS,
|
|
140
|
+
handle: mock(async () => ({ chats: [] })),
|
|
141
|
+
};
|
|
142
|
+
mediator.registerQueryHandler(mockHandler as any);
|
|
143
|
+
|
|
144
|
+
await command.execute([], context);
|
|
145
|
+
|
|
146
|
+
const output = logSpy.mock.calls.map((c) => c[0]).join("\n");
|
|
147
|
+
expect(output).toContain("No conversations found");
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("applies --all-profiles flag in query", async () => {
|
|
151
|
+
const mockHandler = {
|
|
152
|
+
queryType: QUERY_TYPES.LIST_CHATS,
|
|
153
|
+
handle: mock(async () => ({ chats: [] })),
|
|
154
|
+
};
|
|
155
|
+
mediator.registerQueryHandler(mockHandler as any);
|
|
156
|
+
|
|
157
|
+
await command.execute(["--all-profiles"], context);
|
|
158
|
+
|
|
159
|
+
expect(mockHandler.handle).toHaveBeenCalledWith(
|
|
160
|
+
expect.objectContaining({
|
|
161
|
+
payload: expect.objectContaining({ allProfiles: true }),
|
|
162
|
+
}),
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("applies --offset flag", async () => {
|
|
167
|
+
const mockHandler = {
|
|
168
|
+
queryType: QUERY_TYPES.LIST_CHATS,
|
|
169
|
+
handle: mock(async () => ({ chats: SAMPLE_CHATS })),
|
|
170
|
+
};
|
|
171
|
+
mediator.registerQueryHandler(mockHandler as any);
|
|
172
|
+
|
|
173
|
+
await command.execute(["--offset", "2", "--limit", "1"], context);
|
|
174
|
+
|
|
175
|
+
const output = logSpy.mock.calls.map((c) => c[0]).join("\n");
|
|
176
|
+
expect(output).toContain("ghi789");
|
|
177
|
+
expect(output).toContain("Total: 1 conversation");
|
|
178
|
+
});
|
|
179
|
+
});
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { describe, test, expect, mock, beforeEach, afterEach, spyOn } from "bun:test";
|
|
2
|
+
import { NewCommand } from "../../src/cli/commands/new-command.ts";
|
|
3
|
+
import { Mediator } from "../../src/core/mediator.ts";
|
|
4
|
+
import type { CliCommandContext } from "../../src/cli/command-registry.ts";
|
|
5
|
+
import { COMMAND_TYPES } from "../../src/core/command-handlers.ts";
|
|
6
|
+
|
|
7
|
+
describe("NewCommand", () => {
|
|
8
|
+
let command: NewCommand;
|
|
9
|
+
let mediator: Mediator;
|
|
10
|
+
let context: CliCommandContext;
|
|
11
|
+
let logSpy: ReturnType<typeof spyOn>;
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
command = new NewCommand();
|
|
15
|
+
mediator = new Mediator();
|
|
16
|
+
context = {
|
|
17
|
+
verbose: false,
|
|
18
|
+
mediator,
|
|
19
|
+
profileAuthManager: {
|
|
20
|
+
getActiveProfiles: mock(() => ["default"]),
|
|
21
|
+
findProfileForConversation: mock(() => null),
|
|
22
|
+
ensureAuthenticated: mock(() => ({ secure_1psid: "", secure_1psidts: null })),
|
|
23
|
+
} as unknown as CliCommandContext["profileAuthManager"],
|
|
24
|
+
};
|
|
25
|
+
logSpy = spyOn(console, "log").mockImplementation(() => {});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
mock.restore();
|
|
30
|
+
logSpy.mockRestore();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("has correct name and description", () => {
|
|
34
|
+
expect(command.name).toBe("new");
|
|
35
|
+
expect(command.description).toBe("Start a new conversation");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("shows help with --help flag", async () => {
|
|
39
|
+
await command.execute(["--help"], context);
|
|
40
|
+
const output = logSpy.mock.calls.map((c) => c[0]).join("\n");
|
|
41
|
+
expect(output).toContain("Usage: gemiterm new");
|
|
42
|
+
expect(output).toContain("--profile");
|
|
43
|
+
expect(output).toContain("message");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("shows help with -h flag", async () => {
|
|
47
|
+
await command.execute(["-h"], context);
|
|
48
|
+
const output = logSpy.mock.calls.map((c) => c[0]).join("\n");
|
|
49
|
+
expect(output).toContain("Usage: gemiterm new");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("sends start-new-chat command with message", async () => {
|
|
53
|
+
const mockHandler = {
|
|
54
|
+
commandType: COMMAND_TYPES.START_NEW_CHAT,
|
|
55
|
+
handle: mock(async () => ({
|
|
56
|
+
response: "Hello from Gemini!",
|
|
57
|
+
conversationId: "conv-123",
|
|
58
|
+
})),
|
|
59
|
+
};
|
|
60
|
+
mediator.registerCommandHandler(mockHandler as any);
|
|
61
|
+
|
|
62
|
+
await command.execute(["Hello there"], context);
|
|
63
|
+
|
|
64
|
+
expect(mockHandler.handle).toHaveBeenCalledWith(
|
|
65
|
+
expect.objectContaining({
|
|
66
|
+
type: COMMAND_TYPES.START_NEW_CHAT,
|
|
67
|
+
payload: expect.objectContaining({
|
|
68
|
+
message: "Hello there",
|
|
69
|
+
}),
|
|
70
|
+
}),
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const output = logSpy.mock.calls.map((c) => c[0]).join("\n");
|
|
74
|
+
expect(output).toContain("conv-123");
|
|
75
|
+
expect(output).toContain("Hello from Gemini!");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("sends start-new-chat command with profile specified via -p", async () => {
|
|
79
|
+
const mockHandler = {
|
|
80
|
+
commandType: COMMAND_TYPES.START_NEW_CHAT,
|
|
81
|
+
handle: mock(async () => ({
|
|
82
|
+
response: "Hello from Gemini!",
|
|
83
|
+
conversationId: "conv-456",
|
|
84
|
+
})),
|
|
85
|
+
};
|
|
86
|
+
mediator.registerCommandHandler(mockHandler as any);
|
|
87
|
+
|
|
88
|
+
await command.execute(["-p", "dhb-worker", "a new test message"], context);
|
|
89
|
+
|
|
90
|
+
expect(mockHandler.handle).toHaveBeenCalledWith(
|
|
91
|
+
expect.objectContaining({
|
|
92
|
+
type: COMMAND_TYPES.START_NEW_CHAT,
|
|
93
|
+
payload: expect.objectContaining({
|
|
94
|
+
message: "a new test message",
|
|
95
|
+
profileName: "dhb-worker",
|
|
96
|
+
}),
|
|
97
|
+
}),
|
|
98
|
+
);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("sends start-new-chat command with profile specified via --profile", async () => {
|
|
102
|
+
const mockHandler = {
|
|
103
|
+
commandType: COMMAND_TYPES.START_NEW_CHAT,
|
|
104
|
+
handle: mock(async () => ({
|
|
105
|
+
response: "Hello from Gemini!",
|
|
106
|
+
conversationId: "conv-789",
|
|
107
|
+
})),
|
|
108
|
+
};
|
|
109
|
+
mediator.registerCommandHandler(mockHandler as any);
|
|
110
|
+
|
|
111
|
+
await command.execute(["--profile", "my-profile", "test message"], context);
|
|
112
|
+
|
|
113
|
+
expect(mockHandler.handle).toHaveBeenCalledWith(
|
|
114
|
+
expect.objectContaining({
|
|
115
|
+
type: COMMAND_TYPES.START_NEW_CHAT,
|
|
116
|
+
payload: expect.objectContaining({
|
|
117
|
+
message: "test message",
|
|
118
|
+
profileName: "my-profile",
|
|
119
|
+
}),
|
|
120
|
+
}),
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("does not include profileName in payload when no profile specified", async () => {
|
|
125
|
+
const mockHandler = {
|
|
126
|
+
commandType: COMMAND_TYPES.START_NEW_CHAT,
|
|
127
|
+
handle: mock(async () => ({
|
|
128
|
+
response: "Hello",
|
|
129
|
+
conversationId: "conv-abc",
|
|
130
|
+
})),
|
|
131
|
+
};
|
|
132
|
+
mediator.registerCommandHandler(mockHandler as any);
|
|
133
|
+
|
|
134
|
+
await command.execute(["hello"], context);
|
|
135
|
+
|
|
136
|
+
const call = mockHandler.handle.mock.calls[0][0];
|
|
137
|
+
expect(call.payload.profileName).toBeUndefined();
|
|
138
|
+
});
|
|
139
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { describe, test, expect, mock, beforeEach, afterEach, spyOn } from "bun:test";
|
|
2
|
+
import { StatusCommand } from "../../src/cli/commands/status-command.ts";
|
|
3
|
+
import { Mediator } from "../../src/core/mediator.ts";
|
|
4
|
+
import type { CliCommandContext } from "../../src/cli/command-registry.ts";
|
|
5
|
+
import * as configModule from "../../src/infrastructure/config.ts";
|
|
6
|
+
|
|
7
|
+
describe("StatusCommand", () => {
|
|
8
|
+
let command: StatusCommand;
|
|
9
|
+
let context: CliCommandContext;
|
|
10
|
+
let exitSpy: ReturnType<typeof spyOn>;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
command = new StatusCommand();
|
|
14
|
+
context = { verbose: false, mediator: new Mediator() };
|
|
15
|
+
exitSpy = spyOn(process, "exit").mockImplementation((code?: number) => {
|
|
16
|
+
throw new Error(`process.exit(${code})`);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
mock.restore();
|
|
22
|
+
exitSpy.mockRestore();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("has correct name and description", () => {
|
|
26
|
+
expect(command.name).toBe("status");
|
|
27
|
+
expect(command.description).toBe("Show configuration and profile status");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe("execute", () => {
|
|
31
|
+
test("exits with code 2 when no profiles exist", async () => {
|
|
32
|
+
spyOn(configModule, "listProfiles").mockReturnValue([]);
|
|
33
|
+
spyOn(configModule, "getConfigDir").mockReturnValue("/tmp/gemiterm");
|
|
34
|
+
spyOn(configModule, "ensureConfigDir").mockImplementation(() => {});
|
|
35
|
+
spyOn(configModule, "getDefaultProfileName").mockReturnValue("default");
|
|
36
|
+
spyOn(console, "log").mockImplementation(() => {});
|
|
37
|
+
|
|
38
|
+
await expect(command.execute([], context)).rejects.toThrow("process.exit(2)");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("displays config directory and profile table", async () => {
|
|
42
|
+
spyOn(configModule, "listProfiles").mockReturnValue(["default"]);
|
|
43
|
+
spyOn(configModule, "getConfigDir").mockReturnValue("/home/user/.config/gemiterm");
|
|
44
|
+
spyOn(configModule, "ensureConfigDir").mockImplementation(() => {});
|
|
45
|
+
spyOn(configModule, "getDefaultProfileName").mockReturnValue("default");
|
|
46
|
+
|
|
47
|
+
const logOutput: string[] = [];
|
|
48
|
+
spyOn(console, "log").mockImplementation((...args) => {
|
|
49
|
+
logOutput.push(args.join(" "));
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const { ProfileManager } = await import("../../src/infrastructure/storage.ts");
|
|
53
|
+
const mockGetStatus = mock(() => ({
|
|
54
|
+
name: "default",
|
|
55
|
+
exists: true,
|
|
56
|
+
isActive: true,
|
|
57
|
+
expiresAt: "2026-07-01T00:00:00.000Z",
|
|
58
|
+
isDefault: true,
|
|
59
|
+
}));
|
|
60
|
+
const origProto = ProfileManager.prototype.getStatus;
|
|
61
|
+
ProfileManager.prototype.getStatus = mockGetStatus;
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
await command.execute([], context);
|
|
65
|
+
|
|
66
|
+
const combined = logOutput.join("\n");
|
|
67
|
+
expect(combined).toContain("Configuration");
|
|
68
|
+
expect(combined).toContain("/home/user/.config/gemiterm");
|
|
69
|
+
expect(combined).toContain("Profiles");
|
|
70
|
+
expect(combined).toContain("default");
|
|
71
|
+
} finally {
|
|
72
|
+
ProfileManager.prototype.getStatus = origProto;
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("displays multiple profiles with correct default marker", async () => {
|
|
77
|
+
spyOn(configModule, "listProfiles").mockReturnValue(["work", "personal"]);
|
|
78
|
+
spyOn(configModule, "getConfigDir").mockReturnValue("/home/user/.config/gemiterm");
|
|
79
|
+
spyOn(configModule, "ensureConfigDir").mockImplementation(() => {});
|
|
80
|
+
spyOn(configModule, "getDefaultProfileName").mockReturnValue("work");
|
|
81
|
+
|
|
82
|
+
const logOutput: string[] = [];
|
|
83
|
+
spyOn(console, "log").mockImplementation((...args) => {
|
|
84
|
+
logOutput.push(args.join(" "));
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const { ProfileManager } = await import("../../src/infrastructure/storage.ts");
|
|
88
|
+
const statuses = {
|
|
89
|
+
work: { name: "work", exists: true, isActive: true, expiresAt: "2026-08-01T00:00:00.000Z", isDefault: true },
|
|
90
|
+
personal: { name: "personal", exists: true, isActive: false, expiresAt: null, isDefault: false },
|
|
91
|
+
};
|
|
92
|
+
const origProto = ProfileManager.prototype.getStatus;
|
|
93
|
+
ProfileManager.prototype.getStatus = mock((name: string) => statuses[name as keyof typeof statuses]);
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
await command.execute([], context);
|
|
97
|
+
|
|
98
|
+
const combined = logOutput.join("\n");
|
|
99
|
+
expect(combined).toContain("work");
|
|
100
|
+
expect(combined).toContain("personal");
|
|
101
|
+
} finally {
|
|
102
|
+
ProfileManager.prototype.getStatus = origProto;
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { describe, test, expect, mock, beforeEach } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
ListChatsQueryHandler,
|
|
4
|
+
FetchChatQueryHandler,
|
|
5
|
+
GetProfileStatusesQueryHandler,
|
|
6
|
+
GetAuthStatusQueryHandler,
|
|
7
|
+
ListModelsQueryHandler,
|
|
8
|
+
QUERY_TYPES,
|
|
9
|
+
} from "../../src/core/query-handlers.ts";
|
|
10
|
+
import type { IGeminiClientQueryService, IProfileQueryService } from "../../src/core/query-handlers.ts";
|
|
11
|
+
import type { Query } from "../../src/core/mediator.ts";
|
|
12
|
+
|
|
13
|
+
function makeQuery<T>(type: string, payload: T): Query<T> {
|
|
14
|
+
return { type, payload: payload as unknown as Record<string, unknown> };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe("ListChatsQueryHandler", () => {
|
|
18
|
+
let mockClient: IGeminiClientQueryService & { forProfile: (name: string) => IGeminiClientQueryService };
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
mockClient = {
|
|
22
|
+
listChats: mock(() => Promise.resolve([])),
|
|
23
|
+
fetchChat: mock(() => Promise.resolve([])),
|
|
24
|
+
listModels: mock(() => Promise.resolve([])),
|
|
25
|
+
forProfile: (name: string) => mockClient,
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("has correct queryType", () => {
|
|
30
|
+
const handler = new ListChatsQueryHandler(() => mockClient as any, () => []);
|
|
31
|
+
expect(handler.queryType).toBe(QUERY_TYPES.LIST_CHATS);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("returns chats from gemini client", async () => {
|
|
35
|
+
const chats = [
|
|
36
|
+
{ id: "1", title: "Chat 1", isPinned: false, timestamp: 1000 },
|
|
37
|
+
{ id: "2", title: "Chat 2", isPinned: true, timestamp: 2000 },
|
|
38
|
+
];
|
|
39
|
+
mockClient.listChats = mock(() => Promise.resolve(chats));
|
|
40
|
+
|
|
41
|
+
const handler = new ListChatsQueryHandler(() => mockClient as any, () => []);
|
|
42
|
+
const result = await handler.handle(makeQuery(QUERY_TYPES.LIST_CHATS, {}));
|
|
43
|
+
expect(result.chats).toEqual(chats);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("passes limit, offset, search options", async () => {
|
|
47
|
+
mockClient.listChats = mock((opts) => {
|
|
48
|
+
expect(opts).toEqual({ limit: 10, offset: 5, search: "hello" });
|
|
49
|
+
return Promise.resolve([]);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const handler = new ListChatsQueryHandler(() => mockClient as any, () => []);
|
|
53
|
+
await handler.handle(
|
|
54
|
+
makeQuery(QUERY_TYPES.LIST_CHATS, { limit: 10, offset: 5, search: "hello" }),
|
|
55
|
+
);
|
|
56
|
+
expect(mockClient.listChats).toHaveBeenCalledTimes(1);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe("FetchChatQueryHandler", () => {
|
|
61
|
+
let mockClient: IGeminiClientQueryService;
|
|
62
|
+
|
|
63
|
+
beforeEach(() => {
|
|
64
|
+
mockClient = {
|
|
65
|
+
listChats: mock(() => Promise.resolve([])),
|
|
66
|
+
fetchChat: mock(() => Promise.resolve([])),
|
|
67
|
+
listModels: mock(() => Promise.resolve([])),
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("has correct queryType", () => {
|
|
72
|
+
const handler = new FetchChatQueryHandler(mockClient);
|
|
73
|
+
expect(handler.queryType).toBe(QUERY_TYPES.FETCH_CHAT);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("returns messages for conversation", async () => {
|
|
77
|
+
const messages = [
|
|
78
|
+
{ role: "user" as const, content: "Hello" },
|
|
79
|
+
{ role: "model" as const, content: "Hi there!" },
|
|
80
|
+
];
|
|
81
|
+
mockClient.fetchChat = mock(() => Promise.resolve(messages));
|
|
82
|
+
|
|
83
|
+
const handler = new FetchChatQueryHandler(mockClient);
|
|
84
|
+
const result = await handler.handle(
|
|
85
|
+
makeQuery(QUERY_TYPES.FETCH_CHAT, { conversationId: "conv-123" }),
|
|
86
|
+
);
|
|
87
|
+
expect(result.messages).toEqual(messages);
|
|
88
|
+
expect(mockClient.fetchChat).toHaveBeenCalledWith("conv-123");
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe("GetProfileStatusesQueryHandler", () => {
|
|
93
|
+
let mockProfileService: IProfileQueryService;
|
|
94
|
+
|
|
95
|
+
beforeEach(() => {
|
|
96
|
+
mockProfileService = {
|
|
97
|
+
getProfileStatuses: mock(() => Promise.resolve([])),
|
|
98
|
+
getAuthStatus: mock(() => Promise.resolve({ authenticated: false, profileName: null })),
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("has correct queryType", () => {
|
|
103
|
+
const handler = new GetProfileStatusesQueryHandler(mockProfileService);
|
|
104
|
+
expect(handler.queryType).toBe(QUERY_TYPES.GET_PROFILE_STATUSES);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("returns profile statuses", async () => {
|
|
108
|
+
const statuses = [
|
|
109
|
+
{
|
|
110
|
+
name: "default",
|
|
111
|
+
exists: true,
|
|
112
|
+
isActive: true,
|
|
113
|
+
expiresAt: "2099-01-01T00:00:00Z",
|
|
114
|
+
isDefault: true,
|
|
115
|
+
},
|
|
116
|
+
];
|
|
117
|
+
mockProfileService.getProfileStatuses = mock(() => Promise.resolve(statuses));
|
|
118
|
+
|
|
119
|
+
const handler = new GetProfileStatusesQueryHandler(mockProfileService);
|
|
120
|
+
const result = await handler.handle(makeQuery(QUERY_TYPES.GET_PROFILE_STATUSES, {}));
|
|
121
|
+
expect(result.statuses).toEqual(statuses);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe("GetAuthStatusQueryHandler", () => {
|
|
126
|
+
let mockProfileService: IProfileQueryService;
|
|
127
|
+
|
|
128
|
+
beforeEach(() => {
|
|
129
|
+
mockProfileService = {
|
|
130
|
+
getProfileStatuses: mock(() => Promise.resolve([])),
|
|
131
|
+
getAuthStatus: mock(() => Promise.resolve({ authenticated: false, profileName: null })),
|
|
132
|
+
};
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("has correct queryType", () => {
|
|
136
|
+
const handler = new GetAuthStatusQueryHandler(mockProfileService);
|
|
137
|
+
expect(handler.queryType).toBe(QUERY_TYPES.GET_AUTH_STATUS);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("returns authenticated true with profile name", async () => {
|
|
141
|
+
mockProfileService.getAuthStatus = mock(() =>
|
|
142
|
+
Promise.resolve({ authenticated: true, profileName: "default" }),
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
const handler = new GetAuthStatusQueryHandler(mockProfileService);
|
|
146
|
+
const result = await handler.handle(makeQuery(QUERY_TYPES.GET_AUTH_STATUS, {}));
|
|
147
|
+
expect(result.authenticated).toBe(true);
|
|
148
|
+
expect(result.profileName).toBe("default");
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("returns authenticated false with null profile name", async () => {
|
|
152
|
+
mockProfileService.getAuthStatus = mock(() =>
|
|
153
|
+
Promise.resolve({ authenticated: false, profileName: null }),
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
const handler = new GetAuthStatusQueryHandler(mockProfileService);
|
|
157
|
+
const result = await handler.handle(makeQuery(QUERY_TYPES.GET_AUTH_STATUS, {}));
|
|
158
|
+
expect(result.authenticated).toBe(false);
|
|
159
|
+
expect(result.profileName).toBeNull();
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe("ListModelsQueryHandler", () => {
|
|
164
|
+
let mockClient: IGeminiClientQueryService;
|
|
165
|
+
|
|
166
|
+
beforeEach(() => {
|
|
167
|
+
mockClient = {
|
|
168
|
+
listChats: mock(() => Promise.resolve([])),
|
|
169
|
+
fetchChat: mock(() => Promise.resolve([])),
|
|
170
|
+
listModels: mock(() => Promise.resolve([])),
|
|
171
|
+
};
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("has correct queryType", () => {
|
|
175
|
+
const handler = new ListModelsQueryHandler(mockClient);
|
|
176
|
+
expect(handler.queryType).toBe(QUERY_TYPES.LIST_MODELS);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("returns models from gemini client", async () => {
|
|
180
|
+
const models = ["gemini-pro", "gemini-1.5-pro", "gemini-1.5-flash"];
|
|
181
|
+
mockClient.listModels = mock(() => Promise.resolve(models));
|
|
182
|
+
|
|
183
|
+
const handler = new ListModelsQueryHandler(mockClient);
|
|
184
|
+
const result = await handler.handle(makeQuery(QUERY_TYPES.LIST_MODELS, {}));
|
|
185
|
+
expect(result.models).toEqual(models);
|
|
186
|
+
});
|
|
187
|
+
});
|