From 8098dcec9b3b565d29e86d4858baab47ec7235a1 Mon Sep 17 00:00:00 2001 From: Niklas Jensen Date: Tue, 30 Dec 2025 19:33:30 +0100 Subject: [PATCH] Added basic tests for testing a real websocket --- app/env.d.ts | 12 +-- app/package.json | 4 +- app/pnpm-lock.yaml | 17 ++++- app/tests/unit/socket.spec.ts | 135 ++++++++++++++++++++++++++++++++++ app/vitest.config.ts | 16 ++-- 5 files changed, 169 insertions(+), 15 deletions(-) create mode 100644 app/tests/unit/socket.spec.ts diff --git a/app/env.d.ts b/app/env.d.ts index 8c7f1c8..fe77695 100644 --- a/app/env.d.ts +++ b/app/env.d.ts @@ -1,8 +1,8 @@ -declare module 'app-env' { - interface ENV { - VITE_USE_HOST_NAME: boolean - } +declare module "app-env" { + interface ENV { + VITE_USE_HOST_NAME: boolean; + } - const appEnv: ENV - export default appEnv + const appEnv: ENV; + export default appEnv; } diff --git a/app/package.json b/app/package.json index af93a57..d604e89 100644 --- a/app/package.json +++ b/app/package.json @@ -25,6 +25,7 @@ "@sveltejs/vite-plugin-svelte": "^6.2.1", "@types/eslint": "^9.6.1", "@types/three": "^0.180.0", + "@types/ws": "^8.18.1", "@typescript-eslint/eslint-plugin": "^8.46.0", "@typescript-eslint/parser": "^8.46.0", "autoprefixer": "^10.4.21", @@ -44,7 +45,8 @@ "typescript-eslint": "^8.51.0", "unplugin-icons": "^22.4.2", "vite": "^7.1.9", - "vitest": "^3.2.4" + "vitest": "^3.2.4", + "ws": "^8.18.3" }, "type": "module", "dependencies": { diff --git a/app/pnpm-lock.yaml b/app/pnpm-lock.yaml index 93b949c..4fd63b9 100644 --- a/app/pnpm-lock.yaml +++ b/app/pnpm-lock.yaml @@ -87,6 +87,9 @@ importers: '@types/three': specifier: ^0.180.0 version: 0.180.0 + '@types/ws': + specifier: ^8.18.1 + version: 8.18.1 '@typescript-eslint/eslint-plugin': specifier: ^8.46.0 version: 8.46.0(@typescript-eslint/parser@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) @@ -147,6 +150,9 @@ importers: vitest: specifier: ^3.2.4 version: 3.2.4(@types/node@24.7.1)(jiti@2.6.1)(jsdom@27.0.0(postcss@8.5.6))(lightningcss@1.30.2)(yaml@2.8.1) + ws: + specifier: ^8.18.3 + version: 8.18.3 packages: @@ -763,6 +769,9 @@ packages: '@types/webxr@0.5.24': resolution: {integrity: sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==} + '@types/ws@8.18.1': + resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + '@typescript-eslint/eslint-plugin@8.46.0': resolution: {integrity: sha512-hA8gxBq4ukonVXPy0OKhiaUh/68D0E88GSmtC1iAEnGaieuDi38LhS7jdCHRLi6ErJBNDGCzvh5EnzdPwUc0DA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2720,7 +2729,6 @@ snapshots: '@types/node@24.7.1': dependencies: undici-types: 7.14.0 - optional: true '@types/stats.js@0.17.4': {} @@ -2736,6 +2744,10 @@ snapshots: '@types/webxr@0.5.24': {} + '@types/ws@8.18.1': + dependencies: + '@types/node': 24.7.1 + '@typescript-eslint/eslint-plugin@8.46.0(@typescript-eslint/parser@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.1 @@ -3999,8 +4011,7 @@ snapshots: ufo@1.6.1: {} - undici-types@7.14.0: - optional: true + undici-types@7.14.0: {} unplugin-icons@22.4.2(svelte@5.39.11): dependencies: diff --git a/app/tests/unit/socket.spec.ts b/app/tests/unit/socket.spec.ts new file mode 100644 index 0000000..51aabbd --- /dev/null +++ b/app/tests/unit/socket.spec.ts @@ -0,0 +1,135 @@ +import { describe, it, expect, beforeEach, afterEach } from 'vitest' +import { WebSocketServer } from 'ws' +import { socket } from '../../src/lib/stores/socket' +import { IMUData, RSSIData, WebsocketMessage } from '../../src/lib/platform_shared/websocket_message' + +// Helper function to create encoded WebSocket messages +function createEncodedMessage(messageType: 'imu' | 'rssi' | 'mode', data: any): Uint8Array { + const message: any = {} + message[messageType] = data + const wsMessage = WebsocketMessage.create(message) + return WebsocketMessage.encode(wsMessage).finish() +} + +describe.sequential('WebSocket Integration Tests', () => { + let wss: WebSocketServer + let TEST_PORT = 8765 + + beforeEach(async () => { + // Use a different port for each test to avoid conflicts + TEST_PORT++ + + // Create real WebSocket server + wss = new WebSocketServer({ port: TEST_PORT }) + + // Wait for server to start + await new Promise((resolve) => { + wss.on('listening', () => resolve()) + }) + }) + + afterEach(async () => { + // Close all connections and server + wss.clients.forEach((client) => client.close()) + await new Promise((resolve) => { + wss.close(() => resolve()) + }) + // Wait a bit for cleanup + await new Promise(resolve => setTimeout(resolve, 100)) + }) + + it('should connect to WebSocket server', async () => { + socket.init(`ws://localhost:${TEST_PORT}`) + + // Wait for connection + await new Promise(resolve => setTimeout(resolve, 100)) + + let isConnected = false + socket.subscribe(value => { + isConnected = value + })() + + expect(isConnected).toBe(true) + }) + + it('should receive and decode IMU data from server', async () => { + let receivedIMUData: any = null + + // Subscribe to IMU messages before connecting + const unsubscribe = socket.on(IMUData, (data) => { + receivedIMUData = data + }) + + // Connect socket + socket.init(`ws://localhost:${TEST_PORT}`) + + // Wait for client to connect + await new Promise((resolve) => { + wss.on('connection', (ws) => { + // Server sends IMU data to client + const imuPayload = { + x: 1.5, + y: 2.5, + z: 3.5, + temp: 25.0 + } + + const encodedMessage = createEncodedMessage('imu', imuPayload) + ws.send(encodedMessage) + + setTimeout(resolve, 50) + }) + }) + + expect(receivedIMUData).toBeDefined() + expect(receivedIMUData.imu?.x).toBe(1.5) + expect(receivedIMUData.imu?.y).toBe(2.5) + expect(receivedIMUData.imu?.z).toBe(3.5) + expect(receivedIMUData.imu?.temp).toBe(25.0) + + unsubscribe() + }) + +}) + +describe('WebsocketMessage Protobuf Encoding/Decoding', () => { + it('should encode and decode IMU data correctly', () => { + const imuData = { + x: 1.5, + y: 2.5, + z: 3.5, + temp: 25.0 + } + + const encoded = IMUData.encode(imuData).finish() + const decoded = IMUData.decode(encoded) + + expect(decoded.x).toBe(imuData.x) + expect(decoded.y).toBe(imuData.y) + expect(decoded.z).toBe(imuData.z) + expect(decoded.temp).toBe(imuData.temp) + }) + + + + it('should encode and decode complete WebsocketMessage', () => { + const original = WebsocketMessage.create({ + imu: { + x: 1.5, + y: 2.5, + z: 3.5, + temp: 25.0 + } + }) + + const encoded = WebsocketMessage.encode(original).finish() + const decoded = WebsocketMessage.decode(encoded) + + expect(decoded.imu).toBeDefined() + expect(decoded.imu?.x).toBe(1.5) + expect(decoded.imu?.y).toBe(2.5) + expect(decoded.imu?.z).toBe(3.5) + expect(decoded.imu?.temp).toBe(25.0) + }) + +}) diff --git a/app/vitest.config.ts b/app/vitest.config.ts index 068c356..fb718fb 100644 --- a/app/vitest.config.ts +++ b/app/vitest.config.ts @@ -1,11 +1,17 @@ import { defineConfig, UserConfigExport } from 'vitest/config' import { svelte } from '@sveltejs/vite-plugin-svelte' +import path from 'path' const config: UserConfigExport = { - plugins: [svelte()], - test: { - globals: true, - environment: 'jsdom' - } + plugins: [svelte()], + resolve: { + alias: { + $lib: path.resolve(__dirname, './src/lib') + } + }, + test: { + globals: true, + environment: 'jsdom' + } } export default defineConfig(config)