Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions backend/src/test/sseService.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { SseService } from "../services/sseService";

type MockRes = {
write: jest.Mock;
end: jest.Mock;
};

const createMockRes = (): MockRes => ({
write: jest.fn(),
end: jest.fn(),
});

describe("SseService", () => {
let sse: SseService;

beforeEach(() => {
sse = new SseService();
});

test("test_subscribe_to_stream_events", () => {
const res = createMockRes();
sse.subscribeToStream("stream-1", res as any);

sse.broadcastToStream("stream-1", { msg: "hello" });

expect(res.write).toHaveBeenCalledWith(
expect.stringContaining("hello")
);
});

test("test_subscribe_to_user_events", () => {
const res = createMockRes();
sse.subscribeToUser("user-1", res as any);

sse.broadcastToUser("user-1", { msg: "user event" });

expect(res.write).toHaveBeenCalledWith(
expect.stringContaining("user event")
);
});

test("test_subscribe_all", () => {
const res = createMockRes();
sse.subscribeAll(res as any);

sse.broadcastAll({ msg: "global" });

expect(res.write).toHaveBeenCalledWith(
expect.stringContaining("global")
);
});

test("test_client_disconnect_cleaned_up", () => {
const res = createMockRes();
sse.subscribeToUser("user-1", res as any);

sse.disconnect(res as any);

expect(sse.getClientCount()).toBe(0);
});

test("test_broadcast_to_multiple_clients", () => {
const res1 = createMockRes();
const res2 = createMockRes();

sse.subscribeToStream("stream-1", res1 as any);
sse.subscribeToStream("stream-1", res2 as any);

sse.broadcastToStream("stream-1", { msg: "multi" });

expect(res1.write).toHaveBeenCalled();
expect(res2.write).toHaveBeenCalled();
});

test("test_no_cross_user_leakage", () => {
const resA = createMockRes();
const resB = createMockRes();

sse.subscribeToUser("user-A", resA as any);
sse.subscribeToUser("user-B", resB as any);

sse.broadcastToUser("user-A", { msg: "secret" });

expect(resA.write).toHaveBeenCalled();
expect(resB.write).not.toHaveBeenCalled();
});
});
62 changes: 62 additions & 0 deletions contracts/stream_contract/src/stream_contract/src/test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#![cfg(test)]

use super::*;
use soroban_sdk::{testutils::Address as _, Address, Env};

/// Simple deterministic pseudo-random generator (no external deps)
fn pseudo_rand(seed: &mut u64) -> u64 {
*seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1);
*seed
}

#[test]
fn fuzz_stream_invariants() {
let env = Env::default();
let mut seed: u64 = 42;

for _ in 0..10_000 {
// Generate random actors
let _sender = Address::generate(&env);
let _recipient = Address::generate(&env);

// Generate random values (bounded to avoid overflow)
let deposited = (pseudo_rand(&mut seed) % 1_000_000) as i128;
let withdrawn = (pseudo_rand(&mut seed) % deposited.max(1) as u64) as i128;

let elapsed_seconds = (pseudo_rand(&mut seed) % 10_000) as i128;
let rate_per_second = (pseudo_rand(&mut seed) % 1_000) as i128;

let claimable = (rate_per_second * elapsed_seconds)
.min(deposited - withdrawn)
.max(0);

let withdrawn_before_cancel = withdrawn;
let cancel_refund = (deposited - withdrawn_before_cancel).max(0);

// 🧠 Invariants

// 1. withdrawn <= deposited
assert!(
withdrawn <= deposited,
"Invariant failed: withdrawn > deposited"
);

// 2. claimable <= (deposited - withdrawn)
assert!(
claimable <= (deposited - withdrawn),
"Invariant failed: claimable exceeds remaining balance"
);

// 3. rate_per_second * elapsed_seconds >= 0
assert!(
rate_per_second * elapsed_seconds >= 0,
"Invariant failed: negative accrual"
);

// 4. cancel_refund + withdrawn_before_cancel <= deposited
assert!(
cancel_refund + withdrawn_before_cancel <= deposited,
"Invariant failed: total payout exceeds deposit"
);
}
}
Loading