mirror of
https://github.com/trycua/computer.git
synced 2026-05-07 15:42:32 -05:00
Finish writing tests for interfaces
This commit is contained in:
@@ -48,7 +48,6 @@
|
||||
"@types/ws": "^8.18.1",
|
||||
"bumpp": "^10.1.0",
|
||||
"happy-dom": "^17.4.7",
|
||||
"msw": "^2.10.2",
|
||||
"tsdown": "^0.11.9",
|
||||
"tsx": "^4.19.4",
|
||||
"typescript": "^5.8.3",
|
||||
|
||||
Generated
+82
-34
@@ -33,9 +33,6 @@ importers:
|
||||
happy-dom:
|
||||
specifier: ^17.4.7
|
||||
version: 17.6.3
|
||||
msw:
|
||||
specifier: ^2.10.2
|
||||
version: 2.10.2(@types/node@22.15.31)(typescript@5.8.3)
|
||||
tsdown:
|
||||
specifier: ^0.11.9
|
||||
version: 0.11.13(typescript@5.8.3)
|
||||
@@ -1420,15 +1417,18 @@ snapshots:
|
||||
'@bundled-es-modules/cookie@2.0.1':
|
||||
dependencies:
|
||||
cookie: 0.7.2
|
||||
optional: true
|
||||
|
||||
'@bundled-es-modules/statuses@1.0.1':
|
||||
dependencies:
|
||||
statuses: 2.0.2
|
||||
optional: true
|
||||
|
||||
'@bundled-es-modules/tough-cookie@0.1.6':
|
||||
dependencies:
|
||||
'@types/tough-cookie': 4.0.5
|
||||
tough-cookie: 4.1.4
|
||||
optional: true
|
||||
|
||||
'@emnapi/core@1.4.3':
|
||||
dependencies:
|
||||
@@ -1602,6 +1602,7 @@ snapshots:
|
||||
'@inquirer/type': 3.0.7(@types/node@22.15.31)
|
||||
optionalDependencies:
|
||||
'@types/node': 22.15.31
|
||||
optional: true
|
||||
|
||||
'@inquirer/core@10.1.13(@types/node@22.15.31)':
|
||||
dependencies:
|
||||
@@ -1615,12 +1616,15 @@ snapshots:
|
||||
yoctocolors-cjs: 2.1.2
|
||||
optionalDependencies:
|
||||
'@types/node': 22.15.31
|
||||
optional: true
|
||||
|
||||
'@inquirer/figures@1.0.12': {}
|
||||
'@inquirer/figures@1.0.12':
|
||||
optional: true
|
||||
|
||||
'@inquirer/type@3.0.7(@types/node@22.15.31)':
|
||||
optionalDependencies:
|
||||
'@types/node': 22.15.31
|
||||
optional: true
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.8':
|
||||
dependencies:
|
||||
@@ -1647,6 +1651,7 @@ snapshots:
|
||||
is-node-process: 1.2.0
|
||||
outvariant: 1.4.3
|
||||
strict-event-emitter: 0.5.1
|
||||
optional: true
|
||||
|
||||
'@napi-rs/wasm-runtime@0.2.11':
|
||||
dependencies:
|
||||
@@ -1655,14 +1660,17 @@ snapshots:
|
||||
'@tybys/wasm-util': 0.9.0
|
||||
optional: true
|
||||
|
||||
'@open-draft/deferred-promise@2.2.0': {}
|
||||
'@open-draft/deferred-promise@2.2.0':
|
||||
optional: true
|
||||
|
||||
'@open-draft/logger@0.3.0':
|
||||
dependencies:
|
||||
is-node-process: 1.2.0
|
||||
outvariant: 1.4.3
|
||||
optional: true
|
||||
|
||||
'@open-draft/until@2.1.0': {}
|
||||
'@open-draft/until@2.1.0':
|
||||
optional: true
|
||||
|
||||
'@oxc-project/types@0.70.0': {}
|
||||
|
||||
@@ -1779,7 +1787,8 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/deep-eql': 4.0.2
|
||||
|
||||
'@types/cookie@0.6.0': {}
|
||||
'@types/cookie@0.6.0':
|
||||
optional: true
|
||||
|
||||
'@types/debug@4.1.12':
|
||||
dependencies:
|
||||
@@ -1799,9 +1808,11 @@ snapshots:
|
||||
dependencies:
|
||||
undici-types: 6.21.0
|
||||
|
||||
'@types/statuses@2.0.6': {}
|
||||
'@types/statuses@2.0.6':
|
||||
optional: true
|
||||
|
||||
'@types/tough-cookie@4.0.5': {}
|
||||
'@types/tough-cookie@4.0.5':
|
||||
optional: true
|
||||
|
||||
'@types/ws@8.18.1':
|
||||
dependencies:
|
||||
@@ -1853,12 +1864,15 @@ snapshots:
|
||||
ansi-escapes@4.3.2:
|
||||
dependencies:
|
||||
type-fest: 0.21.3
|
||||
optional: true
|
||||
|
||||
ansi-regex@5.0.1: {}
|
||||
ansi-regex@5.0.1:
|
||||
optional: true
|
||||
|
||||
ansi-styles@4.3.0:
|
||||
dependencies:
|
||||
color-convert: 2.0.1
|
||||
optional: true
|
||||
|
||||
ansis@4.1.0: {}
|
||||
|
||||
@@ -1926,13 +1940,15 @@ snapshots:
|
||||
dependencies:
|
||||
consola: 3.4.2
|
||||
|
||||
cli-width@4.1.0: {}
|
||||
cli-width@4.1.0:
|
||||
optional: true
|
||||
|
||||
cliui@8.0.1:
|
||||
dependencies:
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
wrap-ansi: 7.0.0
|
||||
optional: true
|
||||
|
||||
color-convert@2.0.1:
|
||||
dependencies:
|
||||
@@ -1954,7 +1970,8 @@ snapshots:
|
||||
|
||||
consola@3.4.2: {}
|
||||
|
||||
cookie@0.7.2: {}
|
||||
cookie@0.7.2:
|
||||
optional: true
|
||||
|
||||
debug@4.4.1:
|
||||
dependencies:
|
||||
@@ -1974,7 +1991,8 @@ snapshots:
|
||||
|
||||
dts-resolver@2.1.1: {}
|
||||
|
||||
emoji-regex@8.0.0: {}
|
||||
emoji-regex@8.0.0:
|
||||
optional: true
|
||||
|
||||
empathic@1.1.0: {}
|
||||
|
||||
@@ -2027,7 +2045,8 @@ snapshots:
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
get-caller-file@2.0.5: {}
|
||||
get-caller-file@2.0.5:
|
||||
optional: true
|
||||
|
||||
get-tsconfig@4.10.1:
|
||||
dependencies:
|
||||
@@ -2042,22 +2061,26 @@ snapshots:
|
||||
nypm: 0.6.0
|
||||
pathe: 2.0.3
|
||||
|
||||
graphql@16.11.0: {}
|
||||
graphql@16.11.0:
|
||||
optional: true
|
||||
|
||||
happy-dom@17.6.3:
|
||||
dependencies:
|
||||
webidl-conversions: 7.0.0
|
||||
whatwg-mimetype: 3.0.0
|
||||
|
||||
headers-polyfill@4.0.3: {}
|
||||
headers-polyfill@4.0.3:
|
||||
optional: true
|
||||
|
||||
hookable@5.5.3: {}
|
||||
|
||||
is-arrayish@0.3.2: {}
|
||||
|
||||
is-fullwidth-code-point@3.0.0: {}
|
||||
is-fullwidth-code-point@3.0.0:
|
||||
optional: true
|
||||
|
||||
is-node-process@1.2.0: {}
|
||||
is-node-process@1.2.0:
|
||||
optional: true
|
||||
|
||||
jiti@2.4.2: {}
|
||||
|
||||
@@ -2099,8 +2122,10 @@ snapshots:
|
||||
typescript: 5.8.3
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
optional: true
|
||||
|
||||
mute-stream@2.0.0: {}
|
||||
mute-stream@2.0.0:
|
||||
optional: true
|
||||
|
||||
nanoid@3.3.11: {}
|
||||
|
||||
@@ -2118,11 +2143,13 @@ snapshots:
|
||||
|
||||
on-exit-leak-free@2.1.2: {}
|
||||
|
||||
outvariant@1.4.3: {}
|
||||
outvariant@1.4.3:
|
||||
optional: true
|
||||
|
||||
package-manager-detector@1.3.0: {}
|
||||
|
||||
path-to-regexp@6.3.0: {}
|
||||
path-to-regexp@6.3.0:
|
||||
optional: true
|
||||
|
||||
pathe@2.0.3: {}
|
||||
|
||||
@@ -2171,12 +2198,15 @@ snapshots:
|
||||
psl@1.15.0:
|
||||
dependencies:
|
||||
punycode: 2.3.1
|
||||
optional: true
|
||||
|
||||
punycode@2.3.1: {}
|
||||
punycode@2.3.1:
|
||||
optional: true
|
||||
|
||||
quansync@0.2.10: {}
|
||||
|
||||
querystringify@2.2.0: {}
|
||||
querystringify@2.2.0:
|
||||
optional: true
|
||||
|
||||
quick-format-unescaped@4.0.4: {}
|
||||
|
||||
@@ -2189,9 +2219,11 @@ snapshots:
|
||||
|
||||
real-require@0.2.0: {}
|
||||
|
||||
require-directory@2.1.1: {}
|
||||
require-directory@2.1.1:
|
||||
optional: true
|
||||
|
||||
requires-port@1.0.0: {}
|
||||
requires-port@1.0.0:
|
||||
optional: true
|
||||
|
||||
resolve-pkg-maps@1.0.0: {}
|
||||
|
||||
@@ -2289,7 +2321,8 @@ snapshots:
|
||||
|
||||
siginfo@2.0.0: {}
|
||||
|
||||
signal-exit@4.1.0: {}
|
||||
signal-exit@4.1.0:
|
||||
optional: true
|
||||
|
||||
simple-swizzle@0.2.2:
|
||||
dependencies:
|
||||
@@ -2305,21 +2338,25 @@ snapshots:
|
||||
|
||||
stackback@0.0.2: {}
|
||||
|
||||
statuses@2.0.2: {}
|
||||
statuses@2.0.2:
|
||||
optional: true
|
||||
|
||||
std-env@3.9.0: {}
|
||||
|
||||
strict-event-emitter@0.5.1: {}
|
||||
strict-event-emitter@0.5.1:
|
||||
optional: true
|
||||
|
||||
string-width@4.2.3:
|
||||
dependencies:
|
||||
emoji-regex: 8.0.0
|
||||
is-fullwidth-code-point: 3.0.0
|
||||
strip-ansi: 6.0.1
|
||||
optional: true
|
||||
|
||||
strip-ansi@6.0.1:
|
||||
dependencies:
|
||||
ansi-regex: 5.0.1
|
||||
optional: true
|
||||
|
||||
strip-literal@3.0.0:
|
||||
dependencies:
|
||||
@@ -2352,6 +2389,7 @@ snapshots:
|
||||
punycode: 2.3.1
|
||||
universalify: 0.2.0
|
||||
url-parse: 1.5.10
|
||||
optional: true
|
||||
|
||||
tsdown@0.11.13(typescript@5.8.3):
|
||||
dependencies:
|
||||
@@ -2387,9 +2425,11 @@ snapshots:
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
||||
type-fest@0.21.3: {}
|
||||
type-fest@0.21.3:
|
||||
optional: true
|
||||
|
||||
type-fest@4.41.0: {}
|
||||
type-fest@4.41.0:
|
||||
optional: true
|
||||
|
||||
typescript@5.8.3: {}
|
||||
|
||||
@@ -2402,12 +2442,14 @@ snapshots:
|
||||
|
||||
undici-types@6.21.0: {}
|
||||
|
||||
universalify@0.2.0: {}
|
||||
universalify@0.2.0:
|
||||
optional: true
|
||||
|
||||
url-parse@1.5.10:
|
||||
dependencies:
|
||||
querystringify: 2.2.0
|
||||
requires-port: 1.0.0
|
||||
optional: true
|
||||
|
||||
vite-node@3.2.3(@types/node@22.15.31)(jiti@2.4.2)(tsx@4.20.2)(yaml@2.8.0):
|
||||
dependencies:
|
||||
@@ -2502,20 +2544,24 @@ snapshots:
|
||||
ansi-styles: 4.3.0
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
optional: true
|
||||
|
||||
wrap-ansi@7.0.0:
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
optional: true
|
||||
|
||||
ws@8.18.2: {}
|
||||
|
||||
y18n@5.0.8: {}
|
||||
y18n@5.0.8:
|
||||
optional: true
|
||||
|
||||
yaml@2.8.0: {}
|
||||
|
||||
yargs-parser@21.1.1: {}
|
||||
yargs-parser@21.1.1:
|
||||
optional: true
|
||||
|
||||
yargs@17.7.2:
|
||||
dependencies:
|
||||
@@ -2526,5 +2572,7 @@ snapshots:
|
||||
string-width: 4.2.3
|
||||
y18n: 5.0.8
|
||||
yargs-parser: 21.1.1
|
||||
optional: true
|
||||
|
||||
yoctocolors-cjs@2.1.2: {}
|
||||
yoctocolors-cjs@2.1.2:
|
||||
optional: true
|
||||
|
||||
@@ -39,6 +39,7 @@ export abstract class BaseComputerInterface {
|
||||
protected ws: WebSocket;
|
||||
protected apiKey?: string;
|
||||
protected vmName?: string;
|
||||
protected secure?: boolean;
|
||||
|
||||
protected logger = pino({ name: "interface-base" });
|
||||
|
||||
@@ -47,13 +48,15 @@ export abstract class BaseComputerInterface {
|
||||
username = "lume",
|
||||
password = "lume",
|
||||
apiKey?: string,
|
||||
vmName?: string
|
||||
vmName?: string,
|
||||
secure?: boolean
|
||||
) {
|
||||
this.ipAddress = ipAddress;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.apiKey = apiKey;
|
||||
this.vmName = vmName;
|
||||
this.secure = secure;
|
||||
|
||||
// Initialize WebSocket with headers if needed
|
||||
const headers: { [key: string]: string } = {};
|
||||
@@ -71,9 +74,15 @@ export abstract class BaseComputerInterface {
|
||||
* Subclasses can override this to customize the URI.
|
||||
*/
|
||||
protected get wsUri(): string {
|
||||
// Use secure WebSocket for cloud provider with API key
|
||||
const protocol = this.apiKey ? "wss" : "ws";
|
||||
const port = this.apiKey ? "8443" : "8000";
|
||||
const protocol = this.secure ? "wss" : "ws";
|
||||
|
||||
// Check if ipAddress already includes a port
|
||||
if (this.ipAddress.includes(":")) {
|
||||
return `${protocol}://${this.ipAddress}/ws`;
|
||||
}
|
||||
|
||||
// Otherwise, append the default port
|
||||
const port = this.secure ? "8443" : "8000";
|
||||
return `${protocol}://${this.ipAddress}:${port}/ws`;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,447 +0,0 @@
|
||||
import {
|
||||
describe,
|
||||
expect,
|
||||
it,
|
||||
beforeEach,
|
||||
afterEach,
|
||||
vi,
|
||||
beforeAll,
|
||||
afterAll,
|
||||
} from "vitest";
|
||||
import { InterfaceFactory } from "../../src/interface/factory.ts";
|
||||
import { OSType } from "../../src/types.ts";
|
||||
import { ws } from "msw";
|
||||
import { setupServer } from "msw/node";
|
||||
|
||||
describe("Interface Integration Tests", () => {
|
||||
const testIp = "192.168.1.100";
|
||||
const testPort = 8000;
|
||||
|
||||
// Create WebSocket server
|
||||
const server = setupServer();
|
||||
|
||||
beforeAll(() => {
|
||||
server.listen({ onUnhandledRequest: "error" });
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
server.close();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// Reset handlers for each test
|
||||
server.resetHandlers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("Cross-platform interface creation", () => {
|
||||
it("should create correct interface for each OS type", async () => {
|
||||
const osTypes = [OSType.MACOS, OSType.LINUX, OSType.WINDOWS];
|
||||
const interfaces: Array<{
|
||||
os: OSType;
|
||||
interface: ReturnType<typeof InterfaceFactory.createInterfaceForOS>;
|
||||
}> = [];
|
||||
|
||||
// Create interfaces for each OS
|
||||
for (const os of osTypes) {
|
||||
const interface_ = InterfaceFactory.createInterfaceForOS(os, testIp);
|
||||
interfaces.push({ os, interface: interface_ });
|
||||
}
|
||||
|
||||
// Verify each interface is created correctly
|
||||
expect(interfaces).toHaveLength(3);
|
||||
for (const { os, interface: iface } of interfaces) {
|
||||
expect(iface).toBeDefined();
|
||||
// Check that the interface name contains the OS type in some form
|
||||
const osName = os.toLowerCase();
|
||||
expect(iface.constructor.name.toLowerCase()).toContain(osName);
|
||||
}
|
||||
});
|
||||
|
||||
it("should handle multiple interfaces with different IPs", async () => {
|
||||
const ips = ["192.168.1.100", "192.168.1.101", "192.168.1.102"];
|
||||
const interfaces = ips.map((ip) =>
|
||||
InterfaceFactory.createInterfaceForOS(OSType.MACOS, ip)
|
||||
);
|
||||
|
||||
// Set up WebSocket handlers for each IP
|
||||
for (const ip of ips) {
|
||||
const wsLink = ws.link(`ws://${ip}:${testPort}/ws`);
|
||||
server.use(
|
||||
wsLink.addEventListener("connection", ({ client }) => {
|
||||
client.addEventListener("message", () => {
|
||||
// Echo back success response
|
||||
client.send(JSON.stringify({ success: true }));
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Connect all interfaces
|
||||
await Promise.all(interfaces.map((iface) => iface.connect()));
|
||||
|
||||
// Verify all are connected
|
||||
for (const iface of interfaces) {
|
||||
expect(iface.isConnected()).toBe(true);
|
||||
}
|
||||
|
||||
// Clean up
|
||||
for (const iface of interfaces) {
|
||||
iface.disconnect();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("Connection management", () => {
|
||||
it("should handle connection lifecycle", async () => {
|
||||
// Set up WebSocket handler
|
||||
const wsLink = ws.link(`ws://${testIp}:${testPort}/ws`);
|
||||
server.use(
|
||||
wsLink.addEventListener("connection", ({ client }) => {
|
||||
client.addEventListener("message", () => {
|
||||
// Echo back success response
|
||||
client.send(JSON.stringify({ success: true }));
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
const interface_ = InterfaceFactory.createInterfaceForOS(
|
||||
OSType.MACOS,
|
||||
testIp
|
||||
);
|
||||
|
||||
// Initially not connected
|
||||
expect(interface_.isConnected()).toBe(false);
|
||||
|
||||
// Connect
|
||||
await interface_.connect();
|
||||
expect(interface_.isConnected()).toBe(true);
|
||||
|
||||
// Disconnect
|
||||
interface_.disconnect();
|
||||
|
||||
// Wait a tick for the close to process
|
||||
await new Promise((resolve) => process.nextTick(resolve));
|
||||
expect(interface_.isConnected()).toBe(false);
|
||||
});
|
||||
|
||||
it("should handle connection errors gracefully", async () => {
|
||||
// Don't register a handler - connection will succeed but no responses
|
||||
const interface_ = InterfaceFactory.createInterfaceForOS(
|
||||
OSType.MACOS,
|
||||
"192.0.2.1" // TEST-NET-1 address
|
||||
);
|
||||
|
||||
// Should connect (WebSocket mock always connects)
|
||||
await interface_.connect();
|
||||
expect(interface_.isConnected()).toBe(true);
|
||||
|
||||
interface_.disconnect();
|
||||
});
|
||||
|
||||
it("should handle secure connections", async () => {
|
||||
const secureIp = "192.0.2.1";
|
||||
const securePort = 8443;
|
||||
|
||||
// Register handler for secure connection
|
||||
const wsLink = ws.link(`wss://${secureIp}:${securePort}/ws`);
|
||||
server.use(
|
||||
wsLink.addEventListener("connection", ({ client }) => {
|
||||
client.addEventListener("message", () => {
|
||||
// Echo back success response
|
||||
client.send(JSON.stringify({ success: true }));
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
const interface_ = InterfaceFactory.createInterfaceForOS(
|
||||
OSType.MACOS,
|
||||
secureIp,
|
||||
"testuser",
|
||||
"testpass"
|
||||
);
|
||||
|
||||
await interface_.connect();
|
||||
expect(interface_.isConnected()).toBe(true);
|
||||
|
||||
interface_.disconnect();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Performance and concurrency", () => {
|
||||
it("should handle rapid command sequences", async () => {
|
||||
const receivedCommands: string[] = [];
|
||||
|
||||
// Set up WebSocket handler that tracks commands
|
||||
const wsLink = ws.link(`ws://${testIp}:${testPort}/ws`);
|
||||
server.use(
|
||||
wsLink.addEventListener("connection", ({ client }) => {
|
||||
client.addEventListener("message", (event) => {
|
||||
const data = JSON.parse(event.data as string);
|
||||
receivedCommands.push(data.action);
|
||||
// Send response with command index
|
||||
client.send(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
data: `Response for ${data.action}`,
|
||||
})
|
||||
);
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
const interface_ = InterfaceFactory.createInterfaceForOS(
|
||||
OSType.MACOS,
|
||||
testIp
|
||||
);
|
||||
await interface_.connect();
|
||||
|
||||
// Send multiple commands rapidly
|
||||
const commands = ["left_click", "right_click", "double_click"];
|
||||
const promises = commands.map((cmd) => {
|
||||
switch (cmd) {
|
||||
case "left_click":
|
||||
return interface_.leftClick(100, 200);
|
||||
case "right_click":
|
||||
return interface_.rightClick(150, 250);
|
||||
case "double_click":
|
||||
return interface_.doubleClick(200, 300);
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
// Verify all commands were received
|
||||
expect(receivedCommands).toHaveLength(3);
|
||||
expect(receivedCommands).toContain("left_click");
|
||||
expect(receivedCommands).toContain("right_click");
|
||||
expect(receivedCommands).toContain("double_click");
|
||||
|
||||
interface_.disconnect();
|
||||
});
|
||||
|
||||
it("should maintain command order with locking", async () => {
|
||||
const receivedCommands: Array<{ action: string; index: number }> = [];
|
||||
|
||||
// Set up WebSocket handler that tracks commands with delay
|
||||
const wsLink = ws.link(`ws://${testIp}:${testPort}/ws`);
|
||||
server.use(
|
||||
wsLink.addEventListener("connection", ({ client }) => {
|
||||
client.addEventListener("message", async (event) => {
|
||||
// Add delay to simulate processing
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
|
||||
const data = JSON.parse(event.data as string);
|
||||
receivedCommands.push({
|
||||
action: data.action,
|
||||
index: data.index,
|
||||
});
|
||||
|
||||
client.send(JSON.stringify({ success: true }));
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
const interface_ = InterfaceFactory.createInterfaceForOS(
|
||||
OSType.MACOS,
|
||||
testIp
|
||||
);
|
||||
await interface_.connect();
|
||||
|
||||
// Helper to send command with index
|
||||
async function sendCommandWithIndex(action: string, index: number) {
|
||||
await interface_.sendCommand({ action, index });
|
||||
}
|
||||
|
||||
// Send commands in sequence
|
||||
await sendCommandWithIndex("command1", 0);
|
||||
await sendCommandWithIndex("command2", 1);
|
||||
await sendCommandWithIndex("command3", 2);
|
||||
|
||||
// Wait for all commands to be processed
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
// Verify commands were received in order
|
||||
expect(receivedCommands).toHaveLength(3);
|
||||
expect(receivedCommands[0]).toEqual({ action: "command1", index: 0 });
|
||||
expect(receivedCommands[1]).toEqual({ action: "command2", index: 1 });
|
||||
expect(receivedCommands[2]).toEqual({ action: "command3", index: 2 });
|
||||
|
||||
interface_.disconnect();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Error handling", () => {
|
||||
it("should handle command failures", async () => {
|
||||
// Set up WebSocket handler that returns errors
|
||||
const wsLink = ws.link(`ws://${testIp}:${testPort}/ws`);
|
||||
server.use(
|
||||
wsLink.addEventListener("connection", ({ client }) => {
|
||||
client.addEventListener("message", (event) => {
|
||||
const data = JSON.parse(event.data as string);
|
||||
|
||||
if (data.action === "fail_command") {
|
||||
client.send(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
error: "Command failed",
|
||||
})
|
||||
);
|
||||
} else {
|
||||
client.send(JSON.stringify({ success: true }));
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
const interface_ = InterfaceFactory.createInterfaceForOS(
|
||||
OSType.MACOS,
|
||||
testIp
|
||||
);
|
||||
await interface_.connect();
|
||||
|
||||
// Send a failing command
|
||||
await expect(
|
||||
interface_.sendCommand({ action: "fail_command" })
|
||||
).rejects.toThrow("Command failed");
|
||||
|
||||
// Verify interface is still connected
|
||||
expect(interface_.isConnected()).toBe(true);
|
||||
|
||||
// Send a successful command
|
||||
const result = await interface_.sendCommand({
|
||||
action: "success_command",
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
|
||||
interface_.disconnect();
|
||||
});
|
||||
|
||||
it("should handle disconnection during command", async () => {
|
||||
// Set up WebSocket handler that captures WebSocket instance
|
||||
const wsLink = ws.link(`ws://${testIp}:${testPort}/ws`);
|
||||
server.use(
|
||||
wsLink.addEventListener("connection", ({ client }) => {
|
||||
client.addEventListener("message", async () => {
|
||||
// Simulate disconnection during command processing
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
client.close();
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
const interface_ = InterfaceFactory.createInterfaceForOS(
|
||||
OSType.MACOS,
|
||||
testIp
|
||||
);
|
||||
await interface_.connect();
|
||||
|
||||
// Send command that will trigger disconnection
|
||||
await expect(
|
||||
interface_.sendCommand({ action: "disconnect_me" })
|
||||
).rejects.toThrow();
|
||||
|
||||
// Wait for close to process
|
||||
await new Promise((resolve) => setTimeout(resolve, 20));
|
||||
|
||||
// Verify interface is disconnected
|
||||
expect(interface_.isConnected()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Feature-specific tests", () => {
|
||||
it("should handle screenshot commands", async () => {
|
||||
// Set up WebSocket handler
|
||||
const wsLink = ws.link(`ws://${testIp}:${testPort}/ws`);
|
||||
server.use(
|
||||
wsLink.addEventListener("connection", ({ client }) => {
|
||||
client.addEventListener("message", () => {
|
||||
// Echo back success response with screenshot data
|
||||
client.send(JSON.stringify({
|
||||
success: true,
|
||||
data: "base64encodedimage"
|
||||
}));
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
const interface_ = InterfaceFactory.createInterfaceForOS(
|
||||
OSType.MACOS,
|
||||
testIp
|
||||
);
|
||||
await interface_.connect();
|
||||
|
||||
const screenshot = await interface_.screenshot();
|
||||
expect(screenshot).toBeInstanceOf(Buffer);
|
||||
expect(screenshot.toString("base64")).toBe("base64encodedimage");
|
||||
|
||||
interface_.disconnect();
|
||||
});
|
||||
|
||||
it("should handle screen size queries", async () => {
|
||||
// Set up WebSocket handler
|
||||
const wsLink = ws.link(`ws://${testIp}:${testPort}/ws`);
|
||||
server.use(
|
||||
wsLink.addEventListener("connection", ({ client }) => {
|
||||
client.addEventListener("message", () => {
|
||||
// Echo back success response with screen size
|
||||
client.send(JSON.stringify({
|
||||
success: true,
|
||||
data: { width: 1920, height: 1080 }
|
||||
}));
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
const interface_ = InterfaceFactory.createInterfaceForOS(
|
||||
OSType.LINUX,
|
||||
testIp
|
||||
);
|
||||
await interface_.connect();
|
||||
|
||||
const screenSize = await interface_.getScreenSize();
|
||||
expect(screenSize).toEqual({ width: 1920, height: 1080 });
|
||||
|
||||
interface_.disconnect();
|
||||
});
|
||||
|
||||
it("should handle file operations", async () => {
|
||||
// Set up WebSocket handler
|
||||
const wsLink = ws.link(`ws://${testIp}:${testPort}/ws`);
|
||||
server.use(
|
||||
wsLink.addEventListener("connection", ({ client }) => {
|
||||
client.addEventListener("message", () => {
|
||||
// Echo back success response with file data
|
||||
client.send(JSON.stringify({
|
||||
success: true,
|
||||
data: "file content"
|
||||
}));
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
const interface_ = InterfaceFactory.createInterfaceForOS(
|
||||
OSType.WINDOWS,
|
||||
testIp
|
||||
);
|
||||
await interface_.connect();
|
||||
|
||||
// Test file exists
|
||||
const exists = await interface_.fileExists("/test/file.txt");
|
||||
expect(exists).toBe(true);
|
||||
|
||||
// Test read text
|
||||
const content = await interface_.readText("/test/file.txt");
|
||||
expect(content).toBe("file content");
|
||||
|
||||
// Test list directory
|
||||
const files = await interface_.listDir("/test");
|
||||
expect(files).toEqual(["file1.txt", "file2.txt", "dir1"]);
|
||||
|
||||
interface_.disconnect();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -4,7 +4,7 @@ import { MacOSComputerInterface } from "../../src/interface/macos.ts";
|
||||
|
||||
describe("LinuxComputerInterface", () => {
|
||||
const testParams = {
|
||||
ipAddress: "192.0.2.1", // TEST-NET-1 address (RFC 5737) - guaranteed not to be routable
|
||||
ipAddress: "test.cua.com", // TEST-NET-1 address (RFC 5737) - guaranteed not to be routable
|
||||
username: "testuser",
|
||||
password: "testpass",
|
||||
apiKey: "test-api-key",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,26 +1,7 @@
|
||||
import { afterAll, afterEach, beforeAll } from "vitest";
|
||||
import { setupServer } from "msw/node";
|
||||
import { ws } from "msw";
|
||||
|
||||
const chat = ws.link("wss://chat.example.com");
|
||||
beforeAll(() => {});
|
||||
|
||||
const wsHandlers = [
|
||||
chat.addEventListener("connection", ({ client }) => {
|
||||
client.addEventListener("message", (event) => {
|
||||
console.log("Received message from client:", event.data);
|
||||
// Echo the received message back to the client
|
||||
client.send(`Server received: ${event.data}`);
|
||||
});
|
||||
}),
|
||||
];
|
||||
afterAll(() => {});
|
||||
|
||||
const server = setupServer(...wsHandlers);
|
||||
|
||||
// Start server before all tests
|
||||
beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
|
||||
|
||||
// Close server after all tests
|
||||
afterAll(() => server.close());
|
||||
|
||||
// Reset handlers after each test for test isolation
|
||||
afterEach(() => server.resetHandlers());
|
||||
afterEach(() => {});
|
||||
|
||||
Reference in New Issue
Block a user