@moltzap/client 2026.511.1 → 2026.519.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/dist/auth.d.ts +12 -6
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +67 -29
- package/dist/auth.js.map +1 -1
- package/dist/auth.test.js +113 -76
- package/dist/auth.test.js.map +1 -1
- package/dist/channel-base/format-cross-conv.d.ts +43 -0
- package/dist/channel-base/format-cross-conv.d.ts.map +1 -0
- package/dist/channel-base/format-cross-conv.js +69 -0
- package/dist/channel-base/format-cross-conv.js.map +1 -0
- package/dist/channel-base/format-group-block.d.ts +43 -0
- package/dist/channel-base/format-group-block.d.ts.map +1 -0
- package/dist/channel-base/format-group-block.js +59 -0
- package/dist/channel-base/format-group-block.js.map +1 -0
- package/dist/channel-base/index.d.ts +21 -0
- package/dist/channel-base/index.d.ts.map +1 -0
- package/dist/channel-base/index.js +24 -0
- package/dist/channel-base/index.js.map +1 -0
- package/dist/channel-base/lease-guard.d.ts +30 -0
- package/dist/channel-base/lease-guard.d.ts.map +1 -0
- package/dist/channel-base/lease-guard.js +40 -0
- package/dist/channel-base/lease-guard.js.map +1 -0
- package/dist/channel-base/lease-store.d.ts +30 -0
- package/dist/channel-base/lease-store.d.ts.map +1 -0
- package/dist/channel-base/lease-store.js +58 -0
- package/dist/channel-base/lease-store.js.map +1 -0
- package/dist/channel-base/lease.d.ts +94 -0
- package/dist/channel-base/lease.d.ts.map +1 -0
- package/dist/channel-base/lease.js +110 -0
- package/dist/channel-base/lease.js.map +1 -0
- package/dist/channel-core-context.test.d.ts +2 -0
- package/dist/channel-core-context.test.d.ts.map +1 -0
- package/dist/channel-core-context.test.js +334 -0
- package/dist/channel-core-context.test.js.map +1 -0
- package/dist/channel-core-dispatch.test.d.ts +2 -0
- package/dist/channel-core-dispatch.test.d.ts.map +1 -0
- package/dist/channel-core-dispatch.test.js +550 -0
- package/dist/channel-core-dispatch.test.js.map +1 -0
- package/dist/channel-core-enrichment.d.ts +8 -0
- package/dist/channel-core-enrichment.d.ts.map +1 -0
- package/dist/channel-core-enrichment.js +134 -0
- package/dist/channel-core-enrichment.js.map +1 -0
- package/dist/channel-core-errors.d.ts +20 -0
- package/dist/channel-core-errors.d.ts.map +1 -0
- package/dist/channel-core-errors.js +12 -0
- package/dist/channel-core-errors.js.map +1 -0
- package/dist/channel-core-test-support.d.ts +48 -0
- package/dist/channel-core-test-support.d.ts.map +1 -0
- package/dist/channel-core-test-support.js +111 -0
- package/dist/channel-core-test-support.js.map +1 -0
- package/dist/channel-core.d.ts +30 -7
- package/dist/channel-core.d.ts.map +1 -1
- package/dist/channel-core.js +214 -237
- package/dist/channel-core.js.map +1 -1
- package/dist/channel-core.test.js +216 -1082
- package/dist/channel-core.test.js.map +1 -1
- package/dist/cli/__tests__/cli-multi-agent.int.test.js +129 -23
- package/dist/cli/__tests__/cli-multi-agent.int.test.js.map +1 -1
- package/dist/cli/commands/conversations-v2.test.js +64 -54
- package/dist/cli/commands/conversations-v2.test.js.map +1 -1
- package/dist/cli/commands/conversations.d.ts +5 -5
- package/dist/cli/commands/conversations.d.ts.map +1 -1
- package/dist/cli/commands/conversations.js +43 -36
- package/dist/cli/commands/conversations.js.map +1 -1
- package/dist/cli/commands/messages.d.ts +4 -4
- package/dist/cli/commands/messages.d.ts.map +1 -1
- package/dist/cli/commands/messages.js +4 -4
- package/dist/cli/commands/messages.js.map +1 -1
- package/dist/cli/commands/messages.test.js +61 -49
- package/dist/cli/commands/messages.test.js.map +1 -1
- package/dist/cli/commands/ping.d.ts +0 -9
- package/dist/cli/commands/ping.d.ts.map +1 -1
- package/dist/cli/commands/ping.js +1 -1
- package/dist/cli/commands/ping.js.map +1 -1
- package/dist/cli/commands/presence.d.ts +1 -1
- package/dist/cli/commands/presence.js +1 -1
- package/dist/cli/commands/register.d.ts +2 -2
- package/dist/cli/commands/register.d.ts.map +1 -1
- package/dist/cli/commands/register.js +98 -77
- package/dist/cli/commands/register.js.map +1 -1
- package/dist/cli/commands/register.test.js +63 -39
- package/dist/cli/commands/register.test.js.map +1 -1
- package/dist/cli/commands/send.d.ts +6 -6
- package/dist/cli/commands/send.js +6 -6
- package/dist/cli/commands/send.test.js +32 -28
- package/dist/cli/commands/send.test.js.map +1 -1
- package/dist/cli/commands/status.d.ts.map +1 -1
- package/dist/cli/commands/status.js +3 -4
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/config.d.ts +0 -2
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +86 -76
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/config.test.js +80 -42
- package/dist/cli/config.test.js.map +1 -1
- package/dist/cli/index.d.ts +1 -16
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +50 -113
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/process-argv.d.ts +2 -0
- package/dist/cli/process-argv.d.ts.map +1 -0
- package/dist/cli/process-argv.js +2 -0
- package/dist/cli/process-argv.js.map +1 -0
- package/dist/cli/profile.d.ts +3 -10
- package/dist/cli/profile.d.ts.map +1 -1
- package/dist/cli/profile.js +105 -104
- package/dist/cli/profile.js.map +1 -1
- package/dist/cli/profile.test.js +338 -232
- package/dist/cli/profile.test.js.map +1 -1
- package/dist/cli/runtime.d.ts +5 -17
- package/dist/cli/runtime.d.ts.map +1 -1
- package/dist/cli/runtime.js +6 -59
- package/dist/cli/runtime.js.map +1 -1
- package/dist/cli/socket-client.d.ts +10 -15
- package/dist/cli/socket-client.d.ts.map +1 -1
- package/dist/cli/socket-client.js +74 -89
- package/dist/cli/socket-client.js.map +1 -1
- package/dist/cli/transport.d.ts +9 -5
- package/dist/cli/transport.d.ts.map +1 -1
- package/dist/cli/transport.js +45 -42
- package/dist/cli/transport.js.map +1 -1
- package/dist/cli/transport.test.js +227 -133
- package/dist/cli/transport.test.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/local-daemon-rpc.d.ts +13 -0
- package/dist/local-daemon-rpc.d.ts.map +1 -0
- package/dist/local-daemon-rpc.js +17 -0
- package/dist/local-daemon-rpc.js.map +1 -0
- package/dist/local-paths.d.ts +7 -0
- package/dist/local-paths.d.ts.map +1 -0
- package/dist/local-paths.js +20 -0
- package/dist/local-paths.js.map +1 -0
- package/dist/runtime/close-info.d.ts +3 -12
- package/dist/runtime/close-info.d.ts.map +1 -1
- package/dist/runtime/close-info.js +28 -39
- package/dist/runtime/close-info.js.map +1 -1
- package/dist/runtime/close-info.test.js +77 -70
- package/dist/runtime/close-info.test.js.map +1 -1
- package/dist/runtime/errors.test.js +42 -28
- package/dist/runtime/errors.test.js.map +1 -1
- package/dist/runtime/frame.d.ts +4 -31
- package/dist/runtime/frame.d.ts.map +1 -1
- package/dist/runtime/frame.js +7 -116
- package/dist/runtime/frame.js.map +1 -1
- package/dist/runtime/frame.test.js +41 -54
- package/dist/runtime/frame.test.js.map +1 -1
- package/dist/runtime/index.d.ts +7 -0
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +7 -11
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/local-history.d.ts +67 -0
- package/dist/runtime/local-history.d.ts.map +1 -0
- package/dist/runtime/local-history.js +77 -0
- package/dist/runtime/local-history.js.map +1 -0
- package/dist/runtime/local-service-commands.d.ts +19 -0
- package/dist/runtime/local-service-commands.d.ts.map +1 -1
- package/dist/runtime/local-service-commands.js +23 -0
- package/dist/runtime/local-service-commands.js.map +1 -1
- package/dist/runtime/local-socket-server.d.ts +20 -0
- package/dist/runtime/local-socket-server.d.ts.map +1 -0
- package/dist/runtime/local-socket-server.js +94 -0
- package/dist/runtime/local-socket-server.js.map +1 -0
- package/dist/runtime/refs.d.ts +1 -1
- package/dist/runtime/refs.js +1 -1
- package/dist/runtime/runtime.test.js +29 -21
- package/dist/runtime/runtime.test.js.map +1 -1
- package/dist/runtime/service-helpers.d.ts +32 -0
- package/dist/runtime/service-helpers.d.ts.map +1 -0
- package/dist/runtime/service-helpers.js +85 -0
- package/dist/runtime/service-helpers.js.map +1 -0
- package/dist/runtime/subscribers.d.ts +13 -17
- package/dist/runtime/subscribers.d.ts.map +1 -1
- package/dist/runtime/subscribers.js +37 -32
- package/dist/runtime/subscribers.js.map +1 -1
- package/dist/runtime/subscribers.test.js +139 -131
- package/dist/runtime/subscribers.test.js.map +1 -1
- package/dist/service-socket-path.test.d.ts +2 -0
- package/dist/service-socket-path.test.d.ts.map +1 -0
- package/dist/service-socket-path.test.js +87 -0
- package/dist/service-socket-path.test.js.map +1 -0
- package/dist/service.d.ts +23 -20
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +192 -340
- package/dist/service.js.map +1 -1
- package/dist/service.test.js +652 -653
- package/dist/service.test.js.map +1 -1
- package/dist/test/index.d.ts +30 -14
- package/dist/test/index.d.ts.map +1 -1
- package/dist/test/index.js +25 -21
- package/dist/test/index.js.map +1 -1
- package/dist/test-utils/channel-service-fixture.d.ts +1 -0
- package/dist/test-utils/channel-service-fixture.d.ts.map +1 -1
- package/dist/test-utils/channel-service-fixture.js +139 -98
- package/dist/test-utils/channel-service-fixture.js.map +1 -1
- package/dist/test-utils/conformance-adapter.d.ts.map +1 -1
- package/dist/test-utils/conformance-adapter.js +145 -124
- package/dist/test-utils/conformance-adapter.js.map +1 -1
- package/dist/test-utils/fake-service.d.ts.map +1 -1
- package/dist/test-utils/fake-service.js +3 -3
- package/dist/test-utils/fake-service.js.map +1 -1
- package/dist/test-utils/index.d.ts +25 -0
- package/dist/test-utils/index.d.ts.map +1 -1
- package/dist/test-utils/index.js +25 -8
- package/dist/test-utils/index.js.map +1 -1
- package/dist/ws-client-test-support.d.ts +180 -0
- package/dist/ws-client-test-support.d.ts.map +1 -0
- package/dist/ws-client-test-support.js +399 -0
- package/dist/ws-client-test-support.js.map +1 -0
- package/dist/ws-client.d.ts +59 -32
- package/dist/ws-client.d.ts.map +1 -1
- package/dist/ws-client.js +272 -321
- package/dist/ws-client.js.map +1 -1
- package/dist/ws-client.test.js +388 -800
- package/dist/ws-client.test.js.map +1 -1
- package/package.json +18 -10
package/dist/cli/profile.test.js
CHANGED
|
@@ -1,263 +1,369 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Unit tests for the profile layer. Spec items
|
|
3
|
-
* `--no-persist`), Invariants
|
|
2
|
+
* Unit tests for the profile layer. Spec items Section 5.2 (`--profile`,
|
|
3
|
+
* `--no-persist`), Invariants 4.3 (coexistence), 4.4 (no-disk-write).
|
|
4
4
|
*/
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import { Effect } from "effect";
|
|
9
|
-
import
|
|
5
|
+
import { FileSystem, Path } from "@effect/platform";
|
|
6
|
+
import { NodeContext } from "@effect/platform-node";
|
|
7
|
+
import { it as effectIt } from "@effect/vitest";
|
|
8
|
+
import { Effect, Exit, Schema } from "effect";
|
|
9
|
+
import * as fc from "fast-check";
|
|
10
|
+
import { afterEach, beforeEach, describe, expect, vi } from "vitest";
|
|
10
11
|
import { emitNoPersist, loadLayeredConfig, parseProfileName, ProfileInvalidNameError, ProfileNotFoundError, resolveProfileAuth, writeProfile, } from "./profile.js";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
12
|
+
const it = effectIt.scoped;
|
|
13
|
+
const CONFIG_FILE_NAME = "config.json";
|
|
14
|
+
const CONFIG_HOME_PREFIX = "moltzap-profile-";
|
|
15
|
+
const UTF8 = "utf-8";
|
|
16
|
+
const EMPTY_PROFILE_COUNT = 0;
|
|
17
|
+
const SINGLE_PROFILE_COUNT = 1;
|
|
18
|
+
const VALID_PROFILE_NAME = "alice-bot";
|
|
19
|
+
const ALICE_PROFILE_NAME = "alice";
|
|
20
|
+
const BOB_PROFILE_NAME = "bob";
|
|
21
|
+
const UNKNOWN_PROFILE_NAME = "nobody";
|
|
22
|
+
const GHOST_PROFILE_NAME = "ghost";
|
|
23
|
+
const DEFAULT_PROFILE_NAME = "default";
|
|
24
|
+
const TEST_SERVER_URL = "wss://x";
|
|
25
|
+
const DEFAULT_AGENT_NAME = "a";
|
|
26
|
+
const LEGACY_AGENT_NAME = "legacy";
|
|
27
|
+
const ALICE_AGENT_NAME = "alice";
|
|
28
|
+
const BOB_AGENT_NAME = "bob";
|
|
29
|
+
const DEFAULT_API_KEY = "k";
|
|
30
|
+
const LEGACY_API_KEY = "legacy-key";
|
|
31
|
+
const ALICE_API_KEY = "k-alice";
|
|
32
|
+
const BOB_API_KEY = "k-bob";
|
|
33
|
+
const BAD_PROFILE_NAME = "Bad";
|
|
34
|
+
const BAD_PROFILE_REASON = "upper";
|
|
35
|
+
const INVALID_JSON_TEXT = "{not json}";
|
|
36
|
+
const PROFILE_INVALID_NAME_ERROR = "ProfileInvalidNameError";
|
|
37
|
+
const PROFILE_CONFIG_READ_ERROR = "ProfileConfigReadError";
|
|
38
|
+
const PROFILE_NOT_FOUND_ERROR = "ProfileNotFoundError";
|
|
39
|
+
const WrittenProfileRecordSchema = Schema.Struct({
|
|
40
|
+
apiKey: Schema.String,
|
|
41
|
+
agentName: Schema.String,
|
|
42
|
+
serverUrl: Schema.String,
|
|
43
|
+
registeredAt: Schema.optional(Schema.String),
|
|
44
|
+
});
|
|
45
|
+
const WrittenConfigSchema = Schema.Struct({
|
|
46
|
+
apiKey: Schema.optional(Schema.String),
|
|
47
|
+
profiles: Schema.optional(Schema.Record({
|
|
48
|
+
key: Schema.String,
|
|
49
|
+
value: WrittenProfileRecordSchema,
|
|
50
|
+
})),
|
|
51
|
+
});
|
|
52
|
+
const decodeWrittenConfig = Schema.decodeUnknown(WrittenConfigSchema);
|
|
53
|
+
const profileNameArbitrary = fc.stringMatching(/^[a-z0-9][a-z0-9-]{1,30}[a-z0-9]$/);
|
|
54
|
+
beforeEach(() => {
|
|
55
|
+
vi.unstubAllEnvs();
|
|
56
|
+
});
|
|
57
|
+
afterEach(() => {
|
|
58
|
+
vi.unstubAllEnvs();
|
|
59
|
+
});
|
|
60
|
+
const withNodeContext = (effect) => effect.pipe(Effect.provide(NodeContext.layer));
|
|
61
|
+
const defaultRecord = (apiKey = DEFAULT_API_KEY) => ({
|
|
62
|
+
apiKey,
|
|
63
|
+
agentName: DEFAULT_AGENT_NAME,
|
|
64
|
+
serverUrl: TEST_SERVER_URL,
|
|
65
|
+
});
|
|
66
|
+
const namedRecord = (apiKey, agentName) => ({
|
|
67
|
+
apiKey,
|
|
68
|
+
agentName,
|
|
69
|
+
serverUrl: TEST_SERVER_URL,
|
|
70
|
+
});
|
|
71
|
+
const legacyConfig = (apiKey = DEFAULT_API_KEY) => ({
|
|
72
|
+
serverUrl: TEST_SERVER_URL,
|
|
73
|
+
apiKey,
|
|
74
|
+
agentName: DEFAULT_AGENT_NAME,
|
|
75
|
+
});
|
|
76
|
+
const namedProfilesConfig = () => ({
|
|
77
|
+
serverUrl: TEST_SERVER_URL,
|
|
78
|
+
profiles: {
|
|
79
|
+
[ALICE_PROFILE_NAME]: namedRecord(ALICE_API_KEY, ALICE_AGENT_NAME),
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
const makeConfigHome = Effect.gen(function* () {
|
|
83
|
+
const fileSystem = yield* FileSystem.FileSystem;
|
|
84
|
+
const configHome = yield* fileSystem.makeTempDirectoryScoped({
|
|
85
|
+
prefix: CONFIG_HOME_PREFIX,
|
|
39
86
|
});
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
87
|
+
vi.stubEnv("MOLTZAP_CONFIG_HOME", configHome);
|
|
88
|
+
return configHome;
|
|
89
|
+
});
|
|
90
|
+
const writeConfigText = (text) => Effect.gen(function* () {
|
|
91
|
+
const fileSystem = yield* FileSystem.FileSystem;
|
|
92
|
+
const path = yield* Path.Path;
|
|
93
|
+
const configHome = yield* makeConfigHome;
|
|
94
|
+
yield* fileSystem.writeFileString(path.join(configHome, CONFIG_FILE_NAME), text);
|
|
95
|
+
return configHome;
|
|
96
|
+
});
|
|
97
|
+
const writeConfigObject = (config) => writeConfigText(JSON.stringify(config));
|
|
98
|
+
const readConfigObject = (configHome) => Effect.gen(function* () {
|
|
99
|
+
const fileSystem = yield* FileSystem.FileSystem;
|
|
100
|
+
const path = yield* Path.Path;
|
|
101
|
+
const text = yield* fileSystem.readFileString(path.join(configHome, CONFIG_FILE_NAME), UTF8);
|
|
102
|
+
const parsed = yield* Effect.try({
|
|
103
|
+
try: () => JSON.parse(text),
|
|
104
|
+
catch: (error) => error,
|
|
43
105
|
});
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
106
|
+
return yield* decodeWrittenConfig(parsed);
|
|
107
|
+
});
|
|
108
|
+
const expectProfiles = (profiles) => {
|
|
109
|
+
expect(profiles).toBeDefined();
|
|
110
|
+
return profiles ?? {};
|
|
111
|
+
};
|
|
112
|
+
const expectFailure = (exit) => {
|
|
113
|
+
expect(Exit.isFailure(exit)).toBe(true);
|
|
114
|
+
};
|
|
115
|
+
const expectFailureContaining = (exit, marker) => {
|
|
116
|
+
expectFailure(exit);
|
|
117
|
+
expect(String(exit)).toContain(marker);
|
|
118
|
+
};
|
|
119
|
+
function expectGeneratedProfileNameParses(raw) {
|
|
120
|
+
const exit = Effect.runSyncExit(parseProfileName(raw));
|
|
121
|
+
expect(Exit.isSuccess(exit)).toBe(true);
|
|
122
|
+
if (Exit.isSuccess(exit)) {
|
|
123
|
+
expect(exit.value).toBe(raw);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function parsesLowercaseAlphanumericName() {
|
|
127
|
+
return Effect.gen(function* () {
|
|
128
|
+
const name = yield* parseProfileName(VALID_PROFILE_NAME);
|
|
129
|
+
expect(name).toBe(VALID_PROFILE_NAME);
|
|
47
130
|
});
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
131
|
+
}
|
|
132
|
+
function generatedValidProfileNamesParse() {
|
|
133
|
+
return Effect.sync(() => {
|
|
134
|
+
fc.assert(fc.property(profileNameArbitrary, expectGeneratedProfileNameParses));
|
|
51
135
|
});
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
136
|
+
}
|
|
137
|
+
function rejectsEmptyName() {
|
|
138
|
+
return Effect.gen(function* () {
|
|
139
|
+
const exit = yield* Effect.exit(parseProfileName(""));
|
|
140
|
+
expectFailure(exit);
|
|
55
141
|
});
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
142
|
+
}
|
|
143
|
+
function rejectsUppercaseName() {
|
|
144
|
+
return Effect.gen(function* () {
|
|
145
|
+
const exit = yield* Effect.exit(parseProfileName(BAD_PROFILE_NAME));
|
|
146
|
+
expectFailure(exit);
|
|
62
147
|
});
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
expect(view.default).toBeUndefined();
|
|
69
|
-
expect(view.profiles.size).toBe(0);
|
|
70
|
-
});
|
|
148
|
+
}
|
|
149
|
+
function rejectsLeadingHyphen() {
|
|
150
|
+
return Effect.gen(function* () {
|
|
151
|
+
const exit = yield* Effect.exit(parseProfileName("-alice"));
|
|
152
|
+
expectFailure(exit);
|
|
71
153
|
});
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}, async () => {
|
|
78
|
-
const view = await Effect.runPromise(loadLayeredConfig);
|
|
79
|
-
expect(view.default?.apiKey).toBe("k1");
|
|
80
|
-
expect(view.default?.agentName).toBe("a1");
|
|
81
|
-
expect(view.serverUrl).toBe("wss://x");
|
|
82
|
-
});
|
|
154
|
+
}
|
|
155
|
+
function rejectsTrailingHyphen() {
|
|
156
|
+
return Effect.gen(function* () {
|
|
157
|
+
const exit = yield* Effect.exit(parseProfileName("alice-"));
|
|
158
|
+
expectFailure(exit);
|
|
83
159
|
});
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
apiKey: "k-alice",
|
|
90
|
-
agentName: "alice",
|
|
91
|
-
serverUrl: "wss://x",
|
|
92
|
-
},
|
|
93
|
-
},
|
|
94
|
-
}, async () => {
|
|
95
|
-
const view = await Effect.runPromise(loadLayeredConfig);
|
|
96
|
-
expect(view.profiles.size).toBe(1);
|
|
97
|
-
const rec = view.profiles.get("alice");
|
|
98
|
-
expect(rec?.apiKey).toBe("k-alice");
|
|
99
|
-
});
|
|
160
|
+
}
|
|
161
|
+
function invalidNameErrorTypeSurfaces() {
|
|
162
|
+
return Effect.gen(function* () {
|
|
163
|
+
const exit = yield* Effect.exit(parseProfileName(""));
|
|
164
|
+
expectFailureContaining(exit, PROFILE_INVALID_NAME_ERROR);
|
|
100
165
|
});
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
166
|
+
}
|
|
167
|
+
function missingFileLoadsEmptyView() {
|
|
168
|
+
return withNodeContext(Effect.gen(function* () {
|
|
169
|
+
yield* makeConfigHome;
|
|
170
|
+
const view = yield* loadLayeredConfig;
|
|
171
|
+
expect(view.default).toBeUndefined();
|
|
172
|
+
expect(view.profiles.size).toBe(EMPTY_PROFILE_COUNT);
|
|
173
|
+
}));
|
|
174
|
+
}
|
|
175
|
+
function malformedConfigFails() {
|
|
176
|
+
return withNodeContext(Effect.gen(function* () {
|
|
177
|
+
yield* writeConfigText(INVALID_JSON_TEXT);
|
|
178
|
+
const exit = yield* Effect.exit(loadLayeredConfig);
|
|
179
|
+
expectFailureContaining(exit, PROFILE_CONFIG_READ_ERROR);
|
|
180
|
+
}));
|
|
181
|
+
}
|
|
182
|
+
function legacyConfigPopulatesDefaultRecord() {
|
|
183
|
+
return withNodeContext(Effect.gen(function* () {
|
|
184
|
+
yield* writeConfigObject(legacyConfig());
|
|
185
|
+
const view = yield* loadLayeredConfig;
|
|
186
|
+
const record = view.default;
|
|
187
|
+
expect(record).toBeDefined();
|
|
188
|
+
if (record !== undefined) {
|
|
189
|
+
expect(record.apiKey).toBe(DEFAULT_API_KEY);
|
|
190
|
+
expect(record.agentName).toBe(DEFAULT_AGENT_NAME);
|
|
191
|
+
}
|
|
192
|
+
expect(view.serverUrl).toBe(TEST_SERVER_URL);
|
|
193
|
+
}));
|
|
194
|
+
}
|
|
195
|
+
function unknownTopLevelKeysAreTolerated() {
|
|
196
|
+
return withNodeContext(Effect.gen(function* () {
|
|
197
|
+
yield* writeConfigObject({
|
|
198
|
+
...legacyConfig(),
|
|
107
199
|
futureExperimentalField: { nested: true },
|
|
108
|
-
}, async () => {
|
|
109
|
-
const view = await Effect.runPromise(loadLayeredConfig);
|
|
110
|
-
expect(view.default?.apiKey).toBe("k");
|
|
111
200
|
});
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
201
|
+
const view = yield* loadLayeredConfig;
|
|
202
|
+
const record = view.default;
|
|
203
|
+
expect(record).toBeDefined();
|
|
204
|
+
if (record !== undefined) {
|
|
205
|
+
expect(record.apiKey).toBe(DEFAULT_API_KEY);
|
|
206
|
+
}
|
|
207
|
+
}));
|
|
208
|
+
}
|
|
209
|
+
function namedProfilesPopulateMap() {
|
|
210
|
+
return withNodeContext(Effect.gen(function* () {
|
|
211
|
+
yield* writeConfigObject(namedProfilesConfig());
|
|
212
|
+
const view = yield* loadLayeredConfig;
|
|
213
|
+
expect(view.profiles.size).toBe(SINGLE_PROFILE_COUNT);
|
|
214
|
+
const record = view.profiles.get(ALICE_PROFILE_NAME);
|
|
215
|
+
expect(record).toBeDefined();
|
|
216
|
+
if (record !== undefined) {
|
|
217
|
+
expect(record.apiKey).toBe(ALICE_API_KEY);
|
|
218
|
+
}
|
|
219
|
+
}));
|
|
220
|
+
}
|
|
221
|
+
function legacyProfileWithoutRegisteredAtDecodes() {
|
|
222
|
+
return withNodeContext(Effect.gen(function* () {
|
|
223
|
+
yield* writeConfigObject({
|
|
224
|
+
serverUrl: TEST_SERVER_URL,
|
|
116
225
|
profiles: {
|
|
117
|
-
|
|
226
|
+
[BOB_PROFILE_NAME]: namedRecord(BOB_API_KEY, BOB_AGENT_NAME),
|
|
118
227
|
},
|
|
119
|
-
}, async () => {
|
|
120
|
-
const view = await Effect.runPromise(loadLayeredConfig);
|
|
121
|
-
const rec = view.profiles.get("bob");
|
|
122
|
-
expect(rec?.registeredAt).toBeUndefined();
|
|
123
228
|
});
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
try {
|
|
130
|
-
fs.writeFileSync(path.join(tmp, "config.json"), "{not json}");
|
|
131
|
-
const exit = await Effect.runPromiseExit(loadLayeredConfig);
|
|
132
|
-
expect(exit._tag).toBe("Failure");
|
|
133
|
-
if (exit._tag === "Failure") {
|
|
134
|
-
const s = JSON.stringify(exit.cause);
|
|
135
|
-
expect(s).toMatch(/ProfileConfigReadError/);
|
|
136
|
-
}
|
|
229
|
+
const view = yield* loadLayeredConfig;
|
|
230
|
+
const record = view.profiles.get(BOB_PROFILE_NAME);
|
|
231
|
+
expect(record).toBeDefined();
|
|
232
|
+
if (record !== undefined) {
|
|
233
|
+
expect(record.registeredAt).toBeUndefined();
|
|
137
234
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
235
|
+
}));
|
|
236
|
+
}
|
|
237
|
+
function defaultProfileAuthResolves() {
|
|
238
|
+
return withNodeContext(Effect.gen(function* () {
|
|
239
|
+
yield* writeConfigObject(legacyConfig());
|
|
240
|
+
const record = yield* resolveProfileAuth(undefined);
|
|
241
|
+
expect(record.apiKey).toBe(DEFAULT_API_KEY);
|
|
242
|
+
}));
|
|
243
|
+
}
|
|
244
|
+
function unknownProfileFails() {
|
|
245
|
+
return withNodeContext(Effect.gen(function* () {
|
|
246
|
+
yield* writeConfigObject(legacyConfig());
|
|
247
|
+
const exit = yield* Effect.exit(resolveProfileAuth(GHOST_PROFILE_NAME));
|
|
248
|
+
expectFailureContaining(exit, PROFILE_NOT_FOUND_ERROR);
|
|
249
|
+
}));
|
|
250
|
+
}
|
|
251
|
+
function namedProfileAuthResolves() {
|
|
252
|
+
return withNodeContext(Effect.gen(function* () {
|
|
253
|
+
yield* writeConfigObject(namedProfilesConfig());
|
|
254
|
+
const record = yield* resolveProfileAuth(ALICE_PROFILE_NAME);
|
|
255
|
+
expect(record.apiKey).toBe(ALICE_API_KEY);
|
|
256
|
+
}));
|
|
257
|
+
}
|
|
258
|
+
function profileNotFoundErrorCarriesName() {
|
|
259
|
+
return Effect.sync(() => {
|
|
260
|
+
const err = new ProfileNotFoundError({ name: UNKNOWN_PROFILE_NAME });
|
|
261
|
+
expect(err.name).toBe(UNKNOWN_PROFILE_NAME);
|
|
155
262
|
});
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
263
|
+
}
|
|
264
|
+
function writeNamedProfilePreservesLegacyTopLevelKeys() {
|
|
265
|
+
return withNodeContext(Effect.gen(function* () {
|
|
266
|
+
const configHome = yield* writeConfigObject({
|
|
267
|
+
serverUrl: TEST_SERVER_URL,
|
|
268
|
+
apiKey: LEGACY_API_KEY,
|
|
269
|
+
agentName: LEGACY_AGENT_NAME,
|
|
164
270
|
});
|
|
271
|
+
yield* writeProfile(ALICE_PROFILE_NAME, namedRecord(ALICE_API_KEY, ALICE_AGENT_NAME));
|
|
272
|
+
const written = yield* readConfigObject(configHome);
|
|
273
|
+
const profiles = expectProfiles(written.profiles);
|
|
274
|
+
expect(written.apiKey).toBe(LEGACY_API_KEY);
|
|
275
|
+
expect(profiles[ALICE_PROFILE_NAME]?.apiKey).toBe(ALICE_API_KEY);
|
|
276
|
+
}));
|
|
277
|
+
}
|
|
278
|
+
function writeDefaultProfileUpdatesTopLevelKeys() {
|
|
279
|
+
return withNodeContext(Effect.gen(function* () {
|
|
280
|
+
const configHome = yield* makeConfigHome;
|
|
281
|
+
yield* writeProfile(DEFAULT_PROFILE_NAME, defaultRecord());
|
|
282
|
+
const written = yield* readConfigObject(configHome);
|
|
283
|
+
expect(written.apiKey).toBe(DEFAULT_API_KEY);
|
|
284
|
+
expect(written.profiles).toBeUndefined();
|
|
285
|
+
}));
|
|
286
|
+
}
|
|
287
|
+
function writeSecondProfilePreservesFirst() {
|
|
288
|
+
return withNodeContext(Effect.gen(function* () {
|
|
289
|
+
const configHome = yield* writeConfigObject(namedProfilesConfig());
|
|
290
|
+
yield* writeProfile(BOB_PROFILE_NAME, namedRecord(BOB_API_KEY, BOB_AGENT_NAME));
|
|
291
|
+
const written = yield* readConfigObject(configHome);
|
|
292
|
+
const profiles = expectProfiles(written.profiles);
|
|
293
|
+
expect(profiles[ALICE_PROFILE_NAME]?.apiKey).toBe(ALICE_API_KEY);
|
|
294
|
+
expect(profiles[BOB_PROFILE_NAME]?.apiKey).toBe(BOB_API_KEY);
|
|
295
|
+
}));
|
|
296
|
+
}
|
|
297
|
+
function emitNoPersistLeavesConfigDirUnchanged() {
|
|
298
|
+
return withNodeContext(Effect.gen(function* () {
|
|
299
|
+
const fileSystem = yield* FileSystem.FileSystem;
|
|
300
|
+
const configHome = yield* makeConfigHome;
|
|
301
|
+
const before = yield* fileSystem.readDirectory(configHome);
|
|
302
|
+
yield* emitNoPersist(defaultRecord());
|
|
303
|
+
const after = yield* fileSystem.readDirectory(configHome);
|
|
304
|
+
expect(after).toEqual(before);
|
|
305
|
+
}));
|
|
306
|
+
}
|
|
307
|
+
function emitNoPersistReturnsRecord() {
|
|
308
|
+
return Effect.gen(function* () {
|
|
309
|
+
const record = defaultRecord();
|
|
310
|
+
const result = yield* emitNoPersist(record);
|
|
311
|
+
expect(result.record).toEqual(record);
|
|
165
312
|
});
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
agentName: "alice",
|
|
173
|
-
serverUrl: "wss://x",
|
|
174
|
-
},
|
|
175
|
-
},
|
|
176
|
-
}, async () => {
|
|
177
|
-
const record = await Effect.runPromise(resolveProfileAuth("alice"));
|
|
178
|
-
expect(record.apiKey).toBe("k-alice");
|
|
313
|
+
}
|
|
314
|
+
function profileInvalidNameErrorCarriesFields() {
|
|
315
|
+
return Effect.sync(() => {
|
|
316
|
+
const err = new ProfileInvalidNameError({
|
|
317
|
+
name: BAD_PROFILE_NAME,
|
|
318
|
+
reason: BAD_PROFILE_REASON,
|
|
179
319
|
});
|
|
320
|
+
expect(err.name).toBe(BAD_PROFILE_NAME);
|
|
321
|
+
expect(err.reason).toBe(BAD_PROFILE_REASON);
|
|
180
322
|
});
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
323
|
+
}
|
|
324
|
+
describe("parseProfileName valid input", () => {
|
|
325
|
+
it("accepts a valid lowercase-alphanumeric name", parsesLowercaseAlphanumericName);
|
|
326
|
+
it("accepts generated valid profile names", generatedValidProfileNamesParse);
|
|
327
|
+
});
|
|
328
|
+
describe("parseProfileName invalid input", () => {
|
|
329
|
+
it("rejects an empty string with ProfileInvalidNameError", rejectsEmptyName);
|
|
330
|
+
it("rejects a name with uppercase letters", rejectsUppercaseName);
|
|
331
|
+
it("rejects a name starting with a hyphen", rejectsLeadingHyphen);
|
|
332
|
+
});
|
|
333
|
+
describe("parseProfileName invalid boundary input", () => {
|
|
334
|
+
it("rejects a name ending with a hyphen", rejectsTrailingHyphen);
|
|
335
|
+
it("fails with ProfileInvalidNameError type", invalidNameErrorTypeSurfaces);
|
|
336
|
+
});
|
|
337
|
+
describe("loadLayeredConfig missing and malformed input", () => {
|
|
338
|
+
it("missing file resolves with empty view", missingFileLoadsEmptyView);
|
|
339
|
+
it("parse error surfaces as ProfileConfigReadError", malformedConfigFails);
|
|
340
|
+
});
|
|
341
|
+
describe("loadLayeredConfig legacy records", () => {
|
|
342
|
+
it("legacy top-level auth populates default", legacyConfigPopulatesDefaultRecord);
|
|
343
|
+
it("tolerates unknown top-level keys", unknownTopLevelKeysAreTolerated);
|
|
344
|
+
});
|
|
345
|
+
describe("loadLayeredConfig named profiles", () => {
|
|
346
|
+
it("profiles object populates the profiles map", namedProfilesPopulateMap);
|
|
347
|
+
it("missing registeredAt on legacy-shaped profile still decodes", legacyProfileWithoutRegisteredAtDecodes);
|
|
348
|
+
});
|
|
349
|
+
describe("resolveProfileAuth", () => {
|
|
350
|
+
it("undefined name returns the default record", defaultProfileAuthResolves);
|
|
351
|
+
it("unknown name returns ProfileNotFoundError", unknownProfileFails);
|
|
352
|
+
it("known name returns the named record", namedProfileAuthResolves);
|
|
353
|
+
});
|
|
354
|
+
describe("ProfileNotFoundError", () => {
|
|
355
|
+
it("carries the requested name", profileNotFoundErrorCarriesName);
|
|
185
356
|
});
|
|
186
357
|
describe("writeProfile", () => {
|
|
187
|
-
it("writing under a named profile leaves top-level apiKey untouched",
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
apiKey: "k-alice",
|
|
191
|
-
agentName: "alice",
|
|
192
|
-
serverUrl: "wss://x",
|
|
193
|
-
}));
|
|
194
|
-
const written = JSON.parse(fs.readFileSync(path.join(tmp, "config.json"), "utf-8"));
|
|
195
|
-
expect(written.apiKey).toBe("legacy-key");
|
|
196
|
-
expect(written.profiles.alice.apiKey).toBe("k-alice");
|
|
197
|
-
});
|
|
198
|
-
});
|
|
199
|
-
it("writing 'default' updates top-level keys (not under profiles)", async () => {
|
|
200
|
-
await withTmpConfigHome(undefined, async (tmp) => {
|
|
201
|
-
await Effect.runPromise(writeProfile("default", {
|
|
202
|
-
apiKey: "k",
|
|
203
|
-
agentName: "a",
|
|
204
|
-
serverUrl: "wss://x",
|
|
205
|
-
}));
|
|
206
|
-
const written = JSON.parse(fs.readFileSync(path.join(tmp, "config.json"), "utf-8"));
|
|
207
|
-
expect(written.apiKey).toBe("k");
|
|
208
|
-
expect(written.profiles).toBeUndefined();
|
|
209
|
-
});
|
|
210
|
-
});
|
|
211
|
-
it("adding a second profile preserves the first", async () => {
|
|
212
|
-
await withTmpConfigHome({
|
|
213
|
-
serverUrl: "wss://x",
|
|
214
|
-
profiles: {
|
|
215
|
-
alice: {
|
|
216
|
-
apiKey: "k-alice",
|
|
217
|
-
agentName: "alice",
|
|
218
|
-
serverUrl: "wss://x",
|
|
219
|
-
},
|
|
220
|
-
},
|
|
221
|
-
}, async (tmp) => {
|
|
222
|
-
await Effect.runPromise(writeProfile("bob", {
|
|
223
|
-
apiKey: "k-bob",
|
|
224
|
-
agentName: "bob",
|
|
225
|
-
serverUrl: "wss://x",
|
|
226
|
-
}));
|
|
227
|
-
const written = JSON.parse(fs.readFileSync(path.join(tmp, "config.json"), "utf-8"));
|
|
228
|
-
expect(written.profiles.alice.apiKey).toBe("k-alice");
|
|
229
|
-
expect(written.profiles.bob.apiKey).toBe("k-bob");
|
|
230
|
-
});
|
|
231
|
-
});
|
|
358
|
+
it("writing under a named profile leaves top-level apiKey untouched", writeNamedProfilePreservesLegacyTopLevelKeys);
|
|
359
|
+
it("writing default updates top-level keys", writeDefaultProfileUpdatesTopLevelKeys);
|
|
360
|
+
it("adding a second profile preserves the first", writeSecondProfilePreservesFirst);
|
|
232
361
|
});
|
|
233
362
|
describe("emitNoPersist", () => {
|
|
234
|
-
it("never writes to
|
|
235
|
-
|
|
236
|
-
const before = fs.readdirSync(tmp);
|
|
237
|
-
await Effect.runPromise(emitNoPersist({
|
|
238
|
-
apiKey: "k",
|
|
239
|
-
agentName: "a",
|
|
240
|
-
serverUrl: "wss://x",
|
|
241
|
-
}));
|
|
242
|
-
const after = fs.readdirSync(tmp);
|
|
243
|
-
expect(after).toEqual(before);
|
|
244
|
-
});
|
|
245
|
-
});
|
|
246
|
-
it("returns the record unchanged for the caller to print", async () => {
|
|
247
|
-
const record = {
|
|
248
|
-
apiKey: "k",
|
|
249
|
-
agentName: "a",
|
|
250
|
-
serverUrl: "wss://x",
|
|
251
|
-
};
|
|
252
|
-
const result = await Effect.runPromise(emitNoPersist(record));
|
|
253
|
-
expect(result.record).toEqual(record);
|
|
254
|
-
});
|
|
363
|
+
it("never writes to the moltzap config directory", emitNoPersistLeavesConfigDirUnchanged);
|
|
364
|
+
it("returns the record unchanged for the caller to print", emitNoPersistReturnsRecord);
|
|
255
365
|
});
|
|
256
366
|
describe("ProfileInvalidNameError", () => {
|
|
257
|
-
it("carries name and reason",
|
|
258
|
-
const err = new ProfileInvalidNameError({ name: "Bad", reason: "upper" });
|
|
259
|
-
expect(err.name).toBe("Bad");
|
|
260
|
-
expect(err.reason).toBe("upper");
|
|
261
|
-
});
|
|
367
|
+
it("carries name and reason", profileInvalidNameErrorCarriesFields);
|
|
262
368
|
});
|
|
263
369
|
//# sourceMappingURL=profile.test.js.map
|