@agentxjs/core 1.9.1-dev
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/package.json +31 -0
- package/src/agent/AgentStateMachine.ts +151 -0
- package/src/agent/README.md +296 -0
- package/src/agent/__tests__/AgentStateMachine.test.ts +346 -0
- package/src/agent/__tests__/createAgent.test.ts +728 -0
- package/src/agent/__tests__/engine/internal/messageAssemblerProcessor.test.ts +567 -0
- package/src/agent/__tests__/engine/internal/stateEventProcessor.test.ts +315 -0
- package/src/agent/__tests__/engine/internal/turnTrackerProcessor.test.ts +340 -0
- package/src/agent/__tests__/engine/mealy/Mealy.test.ts +370 -0
- package/src/agent/__tests__/engine/mealy/Store.test.ts +123 -0
- package/src/agent/__tests__/engine/mealy/combinators.test.ts +322 -0
- package/src/agent/createAgent.ts +467 -0
- package/src/agent/engine/AgentProcessor.ts +106 -0
- package/src/agent/engine/MealyMachine.ts +184 -0
- package/src/agent/engine/internal/index.ts +35 -0
- package/src/agent/engine/internal/messageAssemblerProcessor.ts +550 -0
- package/src/agent/engine/internal/stateEventProcessor.ts +313 -0
- package/src/agent/engine/internal/turnTrackerProcessor.ts +239 -0
- package/src/agent/engine/mealy/Mealy.ts +308 -0
- package/src/agent/engine/mealy/Processor.ts +70 -0
- package/src/agent/engine/mealy/Sink.ts +56 -0
- package/src/agent/engine/mealy/Source.ts +51 -0
- package/src/agent/engine/mealy/Store.ts +98 -0
- package/src/agent/engine/mealy/combinators.ts +176 -0
- package/src/agent/engine/mealy/index.ts +45 -0
- package/src/agent/index.ts +106 -0
- package/src/agent/types/engine.ts +395 -0
- package/src/agent/types/event.ts +478 -0
- package/src/agent/types/index.ts +197 -0
- package/src/agent/types/message.ts +387 -0
- package/src/common/index.ts +8 -0
- package/src/common/logger/ConsoleLogger.ts +137 -0
- package/src/common/logger/LoggerFactoryImpl.ts +123 -0
- package/src/common/logger/index.ts +26 -0
- package/src/common/logger/types.ts +98 -0
- package/src/container/Container.ts +185 -0
- package/src/container/index.ts +44 -0
- package/src/container/types.ts +71 -0
- package/src/driver/index.ts +42 -0
- package/src/driver/types.ts +363 -0
- package/src/event/EventBus.ts +260 -0
- package/src/event/README.md +237 -0
- package/src/event/__tests__/EventBus.test.ts +251 -0
- package/src/event/index.ts +46 -0
- package/src/event/types/agent.ts +512 -0
- package/src/event/types/base.ts +241 -0
- package/src/event/types/bus.ts +429 -0
- package/src/event/types/command.ts +749 -0
- package/src/event/types/container.ts +471 -0
- package/src/event/types/driver.ts +452 -0
- package/src/event/types/index.ts +26 -0
- package/src/event/types/session.ts +314 -0
- package/src/image/Image.ts +203 -0
- package/src/image/index.ts +36 -0
- package/src/image/types.ts +77 -0
- package/src/index.ts +20 -0
- package/src/mq/OffsetGenerator.ts +48 -0
- package/src/mq/README.md +166 -0
- package/src/mq/__tests__/OffsetGenerator.test.ts +121 -0
- package/src/mq/index.ts +18 -0
- package/src/mq/types.ts +172 -0
- package/src/network/RpcClient.ts +455 -0
- package/src/network/index.ts +76 -0
- package/src/network/jsonrpc.ts +336 -0
- package/src/network/protocol.ts +90 -0
- package/src/network/types.ts +284 -0
- package/src/persistence/index.ts +27 -0
- package/src/persistence/types.ts +226 -0
- package/src/runtime/AgentXRuntime.ts +501 -0
- package/src/runtime/index.ts +56 -0
- package/src/runtime/types.ts +236 -0
- package/src/session/Session.ts +71 -0
- package/src/session/index.ts +25 -0
- package/src/session/types.ts +77 -0
- package/src/workspace/index.ts +27 -0
- package/src/workspace/types.ts +131 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mealy.test.ts - Unit tests for Mealy Machine runtime
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach, mock } from "bun:test";
|
|
6
|
+
import { Mealy, createMealy, type MealyConfig } from "../../../engine/mealy/Mealy";
|
|
7
|
+
import { MemoryStore } from "../../../engine/mealy/Store";
|
|
8
|
+
import type { Processor } from "../../../engine/mealy/Processor";
|
|
9
|
+
import type { Sink, SinkDefinition } from "../../../engine/mealy/Sink";
|
|
10
|
+
|
|
11
|
+
// Test types
|
|
12
|
+
interface TestState {
|
|
13
|
+
count: number;
|
|
14
|
+
buffer: string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface TestEvent {
|
|
18
|
+
type: string;
|
|
19
|
+
value?: number;
|
|
20
|
+
text?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Test processor
|
|
24
|
+
const testProcessor: Processor<TestState, TestEvent, TestEvent> = (state, event) => {
|
|
25
|
+
switch (event.type) {
|
|
26
|
+
case "increment":
|
|
27
|
+
return [
|
|
28
|
+
{ ...state, count: state.count + (event.value || 1) },
|
|
29
|
+
[{ type: "incremented", value: state.count + (event.value || 1) }],
|
|
30
|
+
];
|
|
31
|
+
case "append":
|
|
32
|
+
return [
|
|
33
|
+
{ ...state, buffer: [...state.buffer, event.text || ""] },
|
|
34
|
+
[{ type: "appended", text: event.text }],
|
|
35
|
+
];
|
|
36
|
+
case "reset":
|
|
37
|
+
return [{ count: 0, buffer: [] }, [{ type: "reset_done" }]];
|
|
38
|
+
case "multi_output":
|
|
39
|
+
return [state, [{ type: "output_1" }, { type: "output_2" }, { type: "output_3" }]];
|
|
40
|
+
default:
|
|
41
|
+
return [state, []];
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const initialState: TestState = { count: 0, buffer: [] };
|
|
46
|
+
|
|
47
|
+
describe("Mealy", () => {
|
|
48
|
+
let store: MemoryStore<TestState>;
|
|
49
|
+
let config: MealyConfig<TestState, TestEvent>;
|
|
50
|
+
let mealy: Mealy<TestState, TestEvent>;
|
|
51
|
+
|
|
52
|
+
beforeEach(() => {
|
|
53
|
+
store = new MemoryStore<TestState>();
|
|
54
|
+
config = {
|
|
55
|
+
processor: testProcessor,
|
|
56
|
+
store,
|
|
57
|
+
initialState: { ...initialState },
|
|
58
|
+
recursive: false, // Disable recursion for basic tests
|
|
59
|
+
};
|
|
60
|
+
mealy = new Mealy(config);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("process", () => {
|
|
64
|
+
it("should process event and return result", () => {
|
|
65
|
+
const result = mealy.process("agent1", { type: "increment", value: 5 });
|
|
66
|
+
|
|
67
|
+
expect(result.state.count).toBe(5);
|
|
68
|
+
expect(result.outputs).toHaveLength(1);
|
|
69
|
+
expect(result.outputs[0]).toEqual({ type: "incremented", value: 5 });
|
|
70
|
+
expect(result.processCount).toBe(1);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("should persist state between calls", () => {
|
|
74
|
+
mealy.process("agent1", { type: "increment", value: 5 });
|
|
75
|
+
const result = mealy.process("agent1", { type: "increment", value: 3 });
|
|
76
|
+
|
|
77
|
+
expect(result.state.count).toBe(8);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("should isolate state by agent ID", () => {
|
|
81
|
+
mealy.process("agent1", { type: "increment", value: 10 });
|
|
82
|
+
mealy.process("agent2", { type: "increment", value: 20 });
|
|
83
|
+
|
|
84
|
+
expect(mealy.getState("agent1")?.count).toBe(10);
|
|
85
|
+
expect(mealy.getState("agent2")?.count).toBe(20);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("should use initial state for new agents", () => {
|
|
89
|
+
const result = mealy.process("new_agent", { type: "increment", value: 1 });
|
|
90
|
+
|
|
91
|
+
expect(result.state.count).toBe(1); // Started from 0
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("should handle events with no output", () => {
|
|
95
|
+
const result = mealy.process("agent1", { type: "unknown" });
|
|
96
|
+
|
|
97
|
+
expect(result.outputs).toHaveLength(0);
|
|
98
|
+
expect(result.processCount).toBe(1);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should handle events with multiple outputs", () => {
|
|
102
|
+
const result = mealy.process("agent1", { type: "multi_output" });
|
|
103
|
+
|
|
104
|
+
expect(result.outputs).toHaveLength(3);
|
|
105
|
+
expect(result.outputs[0]).toEqual({ type: "output_1" });
|
|
106
|
+
expect(result.outputs[1]).toEqual({ type: "output_2" });
|
|
107
|
+
expect(result.outputs[2]).toEqual({ type: "output_3" });
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe("recursive processing", () => {
|
|
112
|
+
it("should recursively process outputs when recursive=true", () => {
|
|
113
|
+
// Processor that generates chain: A -> B -> C
|
|
114
|
+
const chainProcessor: Processor<{ depth: number }, TestEvent, TestEvent> = (state, event) => {
|
|
115
|
+
if (event.type === "start") {
|
|
116
|
+
return [{ depth: 1 }, [{ type: "level_1" }]];
|
|
117
|
+
}
|
|
118
|
+
if (event.type === "level_1") {
|
|
119
|
+
return [{ depth: 2 }, [{ type: "level_2" }]];
|
|
120
|
+
}
|
|
121
|
+
if (event.type === "level_2") {
|
|
122
|
+
return [{ depth: 3 }, [{ type: "level_3" }]];
|
|
123
|
+
}
|
|
124
|
+
return [state, []];
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const recursiveMealy = createMealy({
|
|
128
|
+
processor: chainProcessor,
|
|
129
|
+
store: new MemoryStore(),
|
|
130
|
+
initialState: { depth: 0 },
|
|
131
|
+
recursive: true,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const result = recursiveMealy.process("agent", { type: "start" });
|
|
135
|
+
|
|
136
|
+
// Should have processed all 3 levels - outputs are accumulated
|
|
137
|
+
expect(result.outputs).toContainEqual({ type: "level_1" });
|
|
138
|
+
expect(result.outputs).toContainEqual({ type: "level_2" });
|
|
139
|
+
expect(result.outputs).toContainEqual({ type: "level_3" });
|
|
140
|
+
|
|
141
|
+
// Note: result.state is from the initial process call (depth=1)
|
|
142
|
+
// but the store has been updated by recursive calls
|
|
143
|
+
expect(result.state.depth).toBe(1); // First call's state
|
|
144
|
+
expect(recursiveMealy.getState("agent")?.depth).toBe(3); // Final state in store
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("should respect maxDepth limit", () => {
|
|
148
|
+
// Processor that generates infinite chain
|
|
149
|
+
const infiniteProcessor: Processor<{ count: number }, TestEvent, TestEvent> = (
|
|
150
|
+
state,
|
|
151
|
+
event
|
|
152
|
+
) => {
|
|
153
|
+
if (event.type.startsWith("level_")) {
|
|
154
|
+
const next = state.count + 1;
|
|
155
|
+
return [{ count: next }, [{ type: `level_${next}` }]];
|
|
156
|
+
}
|
|
157
|
+
return [state, []];
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const limitedMealy = createMealy({
|
|
161
|
+
processor: infiniteProcessor,
|
|
162
|
+
store: new MemoryStore(),
|
|
163
|
+
initialState: { count: 0 },
|
|
164
|
+
recursive: true,
|
|
165
|
+
maxDepth: 5,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const result = limitedMealy.process("agent", { type: "level_0" });
|
|
169
|
+
|
|
170
|
+
// Should stop at maxDepth
|
|
171
|
+
expect(result.state.count).toBeLessThanOrEqual(5);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
describe("sinks", () => {
|
|
176
|
+
it("should send outputs to sink function", () => {
|
|
177
|
+
const sinkOutputs: TestEvent[][] = [];
|
|
178
|
+
const testSink: Sink<TestEvent> = (_id, outputs) => {
|
|
179
|
+
sinkOutputs.push(outputs);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const mealyWithSink = createMealy({
|
|
183
|
+
...config,
|
|
184
|
+
sinks: [testSink],
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
mealyWithSink.process("agent1", { type: "increment", value: 5 });
|
|
188
|
+
|
|
189
|
+
expect(sinkOutputs).toHaveLength(1);
|
|
190
|
+
expect(sinkOutputs[0]).toContainEqual({ type: "incremented", value: 5 });
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("should send outputs to named SinkDefinition", () => {
|
|
194
|
+
const sinkOutputs: TestEvent[][] = [];
|
|
195
|
+
const namedSink: SinkDefinition<TestEvent> = {
|
|
196
|
+
name: "test-sink",
|
|
197
|
+
sink: (_id, outputs) => {
|
|
198
|
+
sinkOutputs.push(outputs);
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const mealyWithSink = createMealy({
|
|
203
|
+
...config,
|
|
204
|
+
sinks: [namedSink],
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
mealyWithSink.process("agent1", { type: "increment", value: 5 });
|
|
208
|
+
|
|
209
|
+
expect(sinkOutputs).toHaveLength(1);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("should filter outputs with SinkDefinition.filter", () => {
|
|
213
|
+
const sinkOutputs: TestEvent[][] = [];
|
|
214
|
+
const filteredSink: SinkDefinition<TestEvent> = {
|
|
215
|
+
name: "filtered-sink",
|
|
216
|
+
filter: (event) => event.type === "appended",
|
|
217
|
+
sink: (_id, outputs) => {
|
|
218
|
+
sinkOutputs.push(outputs);
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const mealyWithSink = createMealy({
|
|
223
|
+
...config,
|
|
224
|
+
sinks: [filteredSink],
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// This should be filtered out
|
|
228
|
+
mealyWithSink.process("agent1", { type: "increment", value: 5 });
|
|
229
|
+
expect(sinkOutputs).toHaveLength(0);
|
|
230
|
+
|
|
231
|
+
// This should pass through
|
|
232
|
+
mealyWithSink.process("agent1", { type: "append", text: "hello" });
|
|
233
|
+
expect(sinkOutputs).toHaveLength(1);
|
|
234
|
+
expect(sinkOutputs[0]).toContainEqual({ type: "appended", text: "hello" });
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("should handle async sinks", async () => {
|
|
238
|
+
const sinkOutputs: TestEvent[][] = [];
|
|
239
|
+
const asyncSink: Sink<TestEvent> = async (_id, outputs) => {
|
|
240
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
241
|
+
sinkOutputs.push(outputs);
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const mealyWithSink = createMealy({
|
|
245
|
+
...config,
|
|
246
|
+
sinks: [asyncSink],
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
mealyWithSink.process("agent1", { type: "increment", value: 5 });
|
|
250
|
+
|
|
251
|
+
// Give async sink time to complete
|
|
252
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
253
|
+
|
|
254
|
+
expect(sinkOutputs).toHaveLength(1);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("should catch and log sink errors", () => {
|
|
258
|
+
const errorSink: Sink<TestEvent> = () => {
|
|
259
|
+
throw new Error("Sink error");
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const mealyWithSink = createMealy({
|
|
263
|
+
...config,
|
|
264
|
+
sinks: [errorSink],
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// Should not throw
|
|
268
|
+
expect(() => {
|
|
269
|
+
mealyWithSink.process("agent1", { type: "increment", value: 5 });
|
|
270
|
+
}).not.toThrow();
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
describe("state management", () => {
|
|
275
|
+
it("should return state with getState", () => {
|
|
276
|
+
mealy.process("agent1", { type: "increment", value: 5 });
|
|
277
|
+
|
|
278
|
+
const state = mealy.getState("agent1");
|
|
279
|
+
|
|
280
|
+
expect(state).toEqual({ count: 5, buffer: [] });
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it("should return undefined for non-existent agent", () => {
|
|
284
|
+
expect(mealy.getState("unknown")).toBeUndefined();
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it("should check existence with hasState", () => {
|
|
288
|
+
expect(mealy.hasState("agent1")).toBe(false);
|
|
289
|
+
|
|
290
|
+
mealy.process("agent1", { type: "increment" });
|
|
291
|
+
|
|
292
|
+
expect(mealy.hasState("agent1")).toBe(true);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it("should remove state with cleanup", () => {
|
|
296
|
+
mealy.process("agent1", { type: "increment" });
|
|
297
|
+
expect(mealy.hasState("agent1")).toBe(true);
|
|
298
|
+
|
|
299
|
+
mealy.cleanup("agent1");
|
|
300
|
+
|
|
301
|
+
expect(mealy.hasState("agent1")).toBe(false);
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
describe("dynamic sink management", () => {
|
|
306
|
+
it("should add sink at runtime", () => {
|
|
307
|
+
const outputs: TestEvent[][] = [];
|
|
308
|
+
const dynamicSink: Sink<TestEvent> = (_id, o) => outputs.push(o);
|
|
309
|
+
|
|
310
|
+
mealy.process("agent1", { type: "increment" }); // No output yet
|
|
311
|
+
expect(outputs).toHaveLength(0);
|
|
312
|
+
|
|
313
|
+
mealy.addSink(dynamicSink);
|
|
314
|
+
mealy.process("agent1", { type: "increment" });
|
|
315
|
+
|
|
316
|
+
expect(outputs).toHaveLength(1);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it("should remove named sink", () => {
|
|
320
|
+
const outputs: TestEvent[][] = [];
|
|
321
|
+
const namedSink: SinkDefinition<TestEvent> = {
|
|
322
|
+
name: "removable",
|
|
323
|
+
sink: (_id, o) => outputs.push(o),
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
mealy.addSink(namedSink);
|
|
327
|
+
mealy.process("agent1", { type: "increment" });
|
|
328
|
+
expect(outputs).toHaveLength(1);
|
|
329
|
+
|
|
330
|
+
const removed = mealy.removeSink("removable");
|
|
331
|
+
expect(removed).toBe(true);
|
|
332
|
+
|
|
333
|
+
mealy.process("agent1", { type: "increment" });
|
|
334
|
+
expect(outputs).toHaveLength(1); // Still 1, no new output
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it("should return false when removing non-existent sink", () => {
|
|
338
|
+
const removed = mealy.removeSink("non-existent");
|
|
339
|
+
expect(removed).toBe(false);
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
describe("createMealy", () => {
|
|
345
|
+
it("should create Mealy instance with factory function", () => {
|
|
346
|
+
const mealy = createMealy({
|
|
347
|
+
processor: testProcessor,
|
|
348
|
+
store: new MemoryStore(),
|
|
349
|
+
initialState,
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
expect(mealy).toBeInstanceOf(Mealy);
|
|
353
|
+
|
|
354
|
+
const result = mealy.process("test", { type: "increment", value: 1 });
|
|
355
|
+
expect(result.state.count).toBe(1);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it("should use default values for optional config", () => {
|
|
359
|
+
const mealy = createMealy({
|
|
360
|
+
processor: testProcessor,
|
|
361
|
+
store: new MemoryStore(),
|
|
362
|
+
initialState,
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
// Default recursive = true, maxDepth = 100
|
|
366
|
+
// Just verify it works
|
|
367
|
+
const result = mealy.process("test", { type: "increment" });
|
|
368
|
+
expect(result.processCount).toBeGreaterThanOrEqual(1);
|
|
369
|
+
});
|
|
370
|
+
});
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Store.test.ts - Unit tests for MemoryStore
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach } from "bun:test";
|
|
6
|
+
import { MemoryStore, type Store } from "../../../engine/mealy/Store";
|
|
7
|
+
|
|
8
|
+
describe("MemoryStore", () => {
|
|
9
|
+
let store: MemoryStore<{ count: number }>;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
store = new MemoryStore<{ count: number }>();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe("get/set", () => {
|
|
16
|
+
it("should return undefined for non-existent key", () => {
|
|
17
|
+
expect(store.get("unknown")).toBeUndefined();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should store and retrieve value", () => {
|
|
21
|
+
store.set("id1", { count: 10 });
|
|
22
|
+
expect(store.get("id1")).toEqual({ count: 10 });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("should overwrite existing value", () => {
|
|
26
|
+
store.set("id1", { count: 10 });
|
|
27
|
+
store.set("id1", { count: 20 });
|
|
28
|
+
expect(store.get("id1")).toEqual({ count: 20 });
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should store multiple keys independently", () => {
|
|
32
|
+
store.set("id1", { count: 1 });
|
|
33
|
+
store.set("id2", { count: 2 });
|
|
34
|
+
store.set("id3", { count: 3 });
|
|
35
|
+
|
|
36
|
+
expect(store.get("id1")).toEqual({ count: 1 });
|
|
37
|
+
expect(store.get("id2")).toEqual({ count: 2 });
|
|
38
|
+
expect(store.get("id3")).toEqual({ count: 3 });
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe("has", () => {
|
|
43
|
+
it("should return false for non-existent key", () => {
|
|
44
|
+
expect(store.has("unknown")).toBe(false);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("should return true for existing key", () => {
|
|
48
|
+
store.set("id1", { count: 10 });
|
|
49
|
+
expect(store.has("id1")).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("delete", () => {
|
|
54
|
+
it("should remove existing key", () => {
|
|
55
|
+
store.set("id1", { count: 10 });
|
|
56
|
+
store.delete("id1");
|
|
57
|
+
expect(store.has("id1")).toBe(false);
|
|
58
|
+
expect(store.get("id1")).toBeUndefined();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should not throw when deleting non-existent key", () => {
|
|
62
|
+
expect(() => store.delete("unknown")).not.toThrow();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe("clear", () => {
|
|
67
|
+
it("should remove all entries", () => {
|
|
68
|
+
store.set("id1", { count: 1 });
|
|
69
|
+
store.set("id2", { count: 2 });
|
|
70
|
+
store.clear();
|
|
71
|
+
|
|
72
|
+
expect(store.has("id1")).toBe(false);
|
|
73
|
+
expect(store.has("id2")).toBe(false);
|
|
74
|
+
expect(store.size).toBe(0);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe("size", () => {
|
|
79
|
+
it("should return 0 for empty store", () => {
|
|
80
|
+
expect(store.size).toBe(0);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("should return correct count", () => {
|
|
84
|
+
store.set("id1", { count: 1 });
|
|
85
|
+
expect(store.size).toBe(1);
|
|
86
|
+
|
|
87
|
+
store.set("id2", { count: 2 });
|
|
88
|
+
expect(store.size).toBe(2);
|
|
89
|
+
|
|
90
|
+
store.delete("id1");
|
|
91
|
+
expect(store.size).toBe(1);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe("keys", () => {
|
|
96
|
+
it("should return empty iterator for empty store", () => {
|
|
97
|
+
const keys = Array.from(store.keys());
|
|
98
|
+
expect(keys).toEqual([]);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should return all stored keys", () => {
|
|
102
|
+
store.set("id1", { count: 1 });
|
|
103
|
+
store.set("id2", { count: 2 });
|
|
104
|
+
|
|
105
|
+
const keys = Array.from(store.keys());
|
|
106
|
+
expect(keys).toContain("id1");
|
|
107
|
+
expect(keys).toContain("id2");
|
|
108
|
+
expect(keys.length).toBe(2);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe("Store interface compliance", () => {
|
|
113
|
+
it("should implement Store interface", () => {
|
|
114
|
+
const genericStore: Store<{ count: number }> = new MemoryStore();
|
|
115
|
+
|
|
116
|
+
genericStore.set("id", { count: 5 });
|
|
117
|
+
expect(genericStore.get("id")).toEqual({ count: 5 });
|
|
118
|
+
expect(genericStore.has("id")).toBe(true);
|
|
119
|
+
genericStore.delete("id");
|
|
120
|
+
expect(genericStore.has("id")).toBe(false);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
});
|