Skip to content

Commit 0c0da0f

Browse files
committed
test: complete coverage of actor tests
1 parent bebcc9a commit 0c0da0f

33 files changed

+2209
-863
lines changed
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { actor, setup } from "actor-core";
2+
3+
// Short timeout actor
4+
const shortTimeoutActor = actor({
5+
state: { value: 0 },
6+
options: {
7+
action: {
8+
timeout: 50, // 50ms timeout
9+
},
10+
},
11+
actions: {
12+
quickAction: async (c) => {
13+
return "quick response";
14+
},
15+
slowAction: async (c) => {
16+
// This action should timeout
17+
await new Promise((resolve) => setTimeout(resolve, 100));
18+
return "slow response";
19+
},
20+
},
21+
});
22+
23+
// Long timeout actor
24+
const longTimeoutActor = actor({
25+
state: { value: 0 },
26+
options: {
27+
action: {
28+
timeout: 200, // 200ms timeout
29+
},
30+
},
31+
actions: {
32+
delayedAction: async (c) => {
33+
// This action should complete within timeout
34+
await new Promise((resolve) => setTimeout(resolve, 100));
35+
return "delayed response";
36+
},
37+
},
38+
});
39+
40+
// Default timeout actor
41+
const defaultTimeoutActor = actor({
42+
state: { value: 0 },
43+
actions: {
44+
normalAction: async (c) => {
45+
await new Promise((resolve) => setTimeout(resolve, 50));
46+
return "normal response";
47+
},
48+
},
49+
});
50+
51+
// Sync actor (timeout shouldn't apply)
52+
const syncActor = actor({
53+
state: { value: 0 },
54+
options: {
55+
action: {
56+
timeout: 50, // 50ms timeout
57+
},
58+
},
59+
actions: {
60+
syncAction: (c) => {
61+
return "sync response";
62+
},
63+
},
64+
});
65+
66+
export const app = setup({
67+
actors: {
68+
shortTimeoutActor,
69+
longTimeoutActor,
70+
defaultTimeoutActor,
71+
syncActor,
72+
},
73+
});
74+
75+
export type App = typeof app;
76+
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { actor, setup, UserError } from "actor-core";
2+
3+
// Actor with synchronous actions
4+
const syncActor = actor({
5+
state: { value: 0 },
6+
actions: {
7+
// Simple synchronous action that returns a value directly
8+
increment: (c, amount: number = 1) => {
9+
c.state.value += amount;
10+
return c.state.value;
11+
},
12+
// Synchronous action that returns an object
13+
getInfo: (c) => {
14+
return {
15+
currentValue: c.state.value,
16+
timestamp: Date.now(),
17+
};
18+
},
19+
// Synchronous action with no return value (void)
20+
reset: (c) => {
21+
c.state.value = 0;
22+
},
23+
},
24+
});
25+
26+
// Actor with asynchronous actions
27+
const asyncActor = actor({
28+
state: { value: 0, data: null as any },
29+
actions: {
30+
// Async action with a delay
31+
delayedIncrement: async (c, amount: number = 1) => {
32+
await Promise.resolve();
33+
c.state.value += amount;
34+
return c.state.value;
35+
},
36+
// Async action that simulates an API call
37+
fetchData: async (c, id: string) => {
38+
await Promise.resolve();
39+
40+
// Simulate response data
41+
const data = { id, timestamp: Date.now() };
42+
c.state.data = data;
43+
return data;
44+
},
45+
// Async action with error handling
46+
asyncWithError: async (c, shouldError: boolean) => {
47+
await Promise.resolve();
48+
49+
if (shouldError) {
50+
throw new UserError("Intentional error");
51+
}
52+
53+
return "Success";
54+
},
55+
},
56+
});
57+
58+
// Actor with promise actions
59+
const promiseActor = actor({
60+
state: { results: [] as string[] },
61+
actions: {
62+
// Action that returns a resolved promise
63+
resolvedPromise: (c) => {
64+
return Promise.resolve("resolved value");
65+
},
66+
// Action that returns a promise that resolves after a delay
67+
delayedPromise: (c): Promise<string> => {
68+
return new Promise<string>((resolve) => {
69+
c.state.results.push("delayed");
70+
resolve("delayed value");
71+
});
72+
},
73+
// Action that returns a rejected promise
74+
rejectedPromise: (c) => {
75+
return Promise.reject(new UserError("promised rejection"));
76+
},
77+
// Action to check the collected results
78+
getResults: (c) => {
79+
return c.state.results;
80+
},
81+
},
82+
});
83+
84+
export const app = setup({
85+
actors: {
86+
syncActor,
87+
asyncActor,
88+
promiseActor,
89+
},
90+
});
91+
92+
export type App = typeof app;
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { actor, setup } from "actor-core";
2+
3+
type ConnState = {
4+
username: string;
5+
role: string;
6+
counter: number;
7+
createdAt: number;
8+
};
9+
10+
const connStateActor = actor({
11+
state: {
12+
sharedCounter: 0,
13+
disconnectionCount: 0,
14+
},
15+
// Define connection state
16+
createConnState: (
17+
c,
18+
{ params }: { params?: { username?: string; role?: string } },
19+
): ConnState => {
20+
return {
21+
username: params?.username || "anonymous",
22+
role: params?.role || "user",
23+
counter: 0,
24+
createdAt: Date.now(),
25+
};
26+
},
27+
// Lifecycle hook when a connection is established
28+
onConnect: (c, conn) => {
29+
// Broadcast event about the new connection
30+
c.broadcast("userConnected", {
31+
id: conn.id,
32+
username: "anonymous",
33+
role: "user",
34+
});
35+
},
36+
// Lifecycle hook when a connection is closed
37+
onDisconnect: (c, conn) => {
38+
c.state.disconnectionCount += 1;
39+
c.broadcast("userDisconnected", {
40+
id: conn.id,
41+
});
42+
},
43+
actions: {
44+
// Action to increment the connection's counter
45+
incrementConnCounter: (c, amount: number = 1) => {
46+
c.conn.state.counter += amount;
47+
},
48+
49+
// Action to increment the shared counter
50+
incrementSharedCounter: (c, amount: number = 1) => {
51+
c.state.sharedCounter += amount;
52+
return c.state.sharedCounter;
53+
},
54+
55+
// Get the connection state
56+
getConnectionState: (c) => {
57+
return { id: c.conn.id, ...c.conn.state };
58+
},
59+
60+
// Check all active connections
61+
getConnectionIds: (c) => {
62+
return c.conns.keys().toArray();
63+
},
64+
65+
// Get disconnection count
66+
getDisconnectionCount: (c) => {
67+
return c.state.disconnectionCount;
68+
},
69+
70+
// Get all active connection states
71+
getAllConnectionStates: (c) => {
72+
return c.conns.entries().map(([id, conn]) => ({ id, ...conn.state })).toArray();
73+
},
74+
75+
// Send message to a specific connection with matching ID
76+
sendToConnection: (c, targetId: string, message: string) => {
77+
if (c.conns.has(targetId)) {
78+
c.conns.get(targetId)!.send("directMessage", { from: c.conn.id, message });
79+
return true;
80+
} else {
81+
return false;
82+
}
83+
},
84+
85+
// Update connection state (simulated for tests)
86+
updateConnection: (
87+
c,
88+
updates: Partial<{ username: string; role: string }>,
89+
) => {
90+
if (updates.username) c.conn.state.username = updates.username;
91+
if (updates.role) c.conn.state.role = updates.role;
92+
return c.conn.state;
93+
},
94+
},
95+
});
96+
97+
export const app = setup({
98+
actors: { connStateActor },
99+
});
100+
101+
export type App = typeof app;
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { actor, setup, UserError } from "actor-core";
2+
3+
const errorHandlingActor = actor({
4+
state: {
5+
errorLog: [] as string[],
6+
},
7+
actions: {
8+
// Action that throws a UserError with just a message
9+
throwSimpleError: () => {
10+
throw new UserError("Simple error message");
11+
},
12+
13+
// Action that throws a UserError with code and metadata
14+
throwDetailedError: () => {
15+
throw new UserError("Detailed error message", {
16+
code: "detailed_error",
17+
metadata: {
18+
reason: "test",
19+
timestamp: Date.now(),
20+
},
21+
});
22+
},
23+
24+
// Action that throws an internal error
25+
throwInternalError: () => {
26+
throw new Error("This is an internal error");
27+
},
28+
29+
// Action that returns successfully
30+
successfulAction: () => {
31+
return "success";
32+
},
33+
34+
// Action that times out (simulated with a long delay)
35+
timeoutAction: async (c) => {
36+
// This action should time out if the timeout is configured
37+
return new Promise((resolve) => {
38+
setTimeout(() => {
39+
resolve("This should not be reached if timeout works");
40+
}, 10000); // 10 seconds
41+
});
42+
},
43+
44+
// Action with configurable delay to test timeout edge cases
45+
delayedAction: async (c, delayMs: number) => {
46+
return new Promise((resolve) => {
47+
setTimeout(() => {
48+
resolve(`Completed after ${delayMs}ms`);
49+
}, delayMs);
50+
});
51+
},
52+
53+
// Log an error for inspection
54+
logError: (c, error: string) => {
55+
c.state.errorLog.push(error);
56+
return c.state.errorLog;
57+
},
58+
59+
// Get the error log
60+
getErrorLog: (c) => {
61+
return c.state.errorLog;
62+
},
63+
64+
// Clear the error log
65+
clearErrorLog: (c) => {
66+
c.state.errorLog = [];
67+
return true;
68+
},
69+
},
70+
options: {
71+
// Set a short timeout for this actor's actions
72+
action: {
73+
timeout: 500, // 500ms timeout for actions
74+
},
75+
},
76+
});
77+
78+
// Actor with custom timeout
79+
const customTimeoutActor = actor({
80+
state: {},
81+
actions: {
82+
quickAction: async () => {
83+
await new Promise((resolve) => setTimeout(resolve, 50));
84+
return "Quick action completed";
85+
},
86+
slowAction: async () => {
87+
await new Promise((resolve) => setTimeout(resolve, 300));
88+
return "Slow action completed";
89+
},
90+
},
91+
options: {
92+
action: {
93+
timeout: 200, // 200ms timeout
94+
},
95+
},
96+
});
97+
98+
export const app = setup({
99+
actors: {
100+
errorHandlingActor,
101+
customTimeoutActor,
102+
},
103+
});
104+
105+
export type App = typeof app;

0 commit comments

Comments
 (0)