Skip to content

Commit 897499d

Browse files
Fix CI: backend test lifespan + frontend authEnabled in mocks
- test_events.py, test_ws_auth.py: noop lifespan to avoid seed_first_run hitting missing tables; mock event bus for LocalEventBus compatibility - AppAuth, ProtectedRoute, UserMenu tests: add authEnabled to mocks - NewProjectPage: fix stale "from_notes" comparison
1 parent c859bce commit 897499d

6 files changed

Lines changed: 52 additions & 25 deletions

File tree

backend/tests/test_events.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Tests for Event Bus, REST events endpoint, and WebSocket streaming."""
22

3+
import contextlib
34
import json
45
import uuid
56
from collections.abc import AsyncGenerator
@@ -119,6 +120,12 @@ async def _override_get_db() -> AsyncGenerator[AsyncSession, None]:
119120

120121
app.dependency_overrides[get_db] = _override_get_db
121122

123+
@contextlib.asynccontextmanager
124+
async def _noop_lifespan(app): # type: ignore[no-untyped-def]
125+
yield
126+
127+
app.router.lifespan_context = _noop_lifespan
128+
122129
transport = ASGITransport(app=app)
123130
async with AsyncClient(transport=transport, base_url="http://test") as ac:
124131
# Register a test user and include auth headers
@@ -343,6 +350,12 @@ async def _override_get_db() -> AsyncGenerator[AsyncSession, None]:
343350

344351
app.dependency_overrides[get_db] = _override_get_db
345352

353+
@contextlib.asynccontextmanager
354+
async def _noop_lifespan(app): # type: ignore[no-untyped-def]
355+
yield
356+
357+
app.router.lifespan_context = _noop_lifespan
358+
346359
# starlette's sync TestClient handles WebSocket testing
347360
with TestClient(app) as tc:
348361
with pytest.raises(Exception):
@@ -366,6 +379,12 @@ async def _override_get_db() -> AsyncGenerator[AsyncSession, None]:
366379

367380
app.dependency_overrides[get_db] = _override_get_db
368381

382+
@contextlib.asynccontextmanager
383+
async def _noop_lifespan(app): # type: ignore[no-untyped-def]
384+
yield
385+
386+
app.router.lifespan_context = _noop_lifespan
387+
369388
token = create_access_token(uuid.uuid4())
370389

371390
with TestClient(app) as tc:

backend/tests/test_ws_auth.py

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
"""Tests for WebSocket JWT authentication (issue #72)."""
22

3+
import contextlib
34
import uuid
45
from collections.abc import AsyncGenerator
56
from datetime import datetime, timedelta, timezone
6-
from unittest.mock import AsyncMock, MagicMock, patch
7+
from unittest.mock import MagicMock, patch
78

89
import pytest
910
import pytest_asyncio
@@ -110,6 +111,12 @@ async def _override_get_db() -> AsyncGenerator[AsyncSession, None]:
110111
yield db_session
111112

112113
application.dependency_overrides[get_db] = _override_get_db
114+
115+
@contextlib.asynccontextmanager
116+
async def _noop_lifespan(app): # type: ignore[no-untyped-def]
117+
yield
118+
119+
application.router.lifespan_context = _noop_lifespan
113120
return application
114121

115122

@@ -128,26 +135,20 @@ def refresh_token() -> str:
128135
return create_refresh_token(uuid.uuid4())
129136

130137

131-
def _make_mock_redis():
132-
"""Create a mock Redis whose listen() yields one test message then stops."""
133-
134-
async def _listen():
135-
# Yield a real message so the handler sends it over the websocket
136-
yield {"type": "message", "data": b'{"event": "test"}'}
137-
138-
mock_pubsub = MagicMock()
139-
mock_pubsub.subscribe = AsyncMock()
140-
mock_pubsub.unsubscribe = AsyncMock()
141-
mock_pubsub.aclose = AsyncMock()
142-
mock_pubsub.listen = _listen
138+
def _make_mock_event_bus():
139+
"""Create a mock event bus whose subscribe() yields one test message then stops."""
140+
import asyncio as _asyncio
141+
from contextlib import asynccontextmanager
143142

144-
mock_redis = MagicMock()
145-
mock_redis.pubsub.return_value = mock_pubsub
146-
mock_redis.close = AsyncMock()
143+
@asynccontextmanager
144+
async def _subscribe(session_id):
145+
queue: _asyncio.Queue[str] = _asyncio.Queue()
146+
await queue.put('{"event": "test"}')
147+
yield queue
147148

148-
mock_redis_class = MagicMock()
149-
mock_redis_class.from_url.return_value = mock_redis
150-
return mock_redis_class
149+
bus = MagicMock()
150+
bus.subscribe = _subscribe
151+
return bus
151152

152153

153154
# ---------------------------------------------------------------------------
@@ -190,7 +191,7 @@ def test_none_token_raises(self):
190191
@pytest.mark.asyncio
191192
class TestWsAuthQueryParam:
192193
async def test_valid_token_accepted(self, app, session_id, valid_token):
193-
with patch("redis.asyncio.Redis", _make_mock_redis()):
194+
with patch("codehive.api.ws.create_event_bus", return_value=_make_mock_event_bus()):
194195
with TestClient(app) as client:
195196
with client.websocket_connect(
196197
f"/api/sessions/{session_id}/ws?token={valid_token}"
@@ -235,7 +236,7 @@ async def test_refresh_token_rejected(self, app, session_id, refresh_token):
235236
@pytest.mark.asyncio
236237
class TestWsAuthFirstMessage:
237238
async def test_valid_auth_message_accepted(self, app, session_id, valid_token):
238-
with patch("redis.asyncio.Redis", _make_mock_redis()):
239+
with patch("codehive.api.ws.create_event_bus", return_value=_make_mock_event_bus()):
239240
with TestClient(app) as client:
240241
with client.websocket_connect(f"/api/sessions/{session_id}/ws") as ws:
241242
ws.send_json({"type": "auth", "token": valid_token})

web/src/pages/NewProjectPage.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
type FlowStartResult,
66
type ProjectBrief,
77
} from "@/api/projectFlow";
8-
import { createProject, fetchDefaultWorkspaceId } from "@/api/projects";
8+
import { createProject } from "@/api/projects";
99
import FlowChat from "@/components/project-flow/FlowChat";
1010
import BriefReview from "@/components/project-flow/BriefReview";
1111

@@ -58,9 +58,7 @@ export default function NewProjectPage() {
5858
setLoading(true);
5959
setError(null);
6060
try {
61-
const wsId = await fetchDefaultWorkspaceId();
6261
const project = await createProject({
63-
workspace_id: wsId,
6462
name: name.trim(),
6563
});
6664
navigate(`/projects/${project.id}`);
@@ -171,7 +169,7 @@ export default function NewProjectPage() {
171169
value={initialInput}
172170
onChange={(e) => setInitialInput(e.target.value)}
173171
placeholder={
174-
selectedType === "from_notes"
172+
selectedType === "spec_from_notes"
175173
? "Paste your notes, ideas, or documentation here..."
176174
: "https://github.com/user/repo"
177175
}

web/src/test/AppAuth.test.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ const authenticatedAuth = {
4040
refreshToken: "ref",
4141
isAuthenticated: true,
4242
isLoading: false,
43+
authEnabled: true,
4344
login: vi.fn(),
4445
register: vi.fn(),
4546
logout: vi.fn(),
@@ -52,6 +53,7 @@ const unauthenticatedAuth = {
5253
refreshToken: null,
5354
isAuthenticated: false,
5455
isLoading: false,
56+
authEnabled: true,
5557
login: vi.fn(),
5658
register: vi.fn(),
5759
logout: vi.fn(),

web/src/test/ProtectedRoute.test.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ describe("ProtectedRoute", () => {
2727
refreshToken: null,
2828
isAuthenticated: false,
2929
isLoading: false,
30+
authEnabled: true,
3031
login: vi.fn(),
3132
register: vi.fn(),
3233
logout: vi.fn(),
@@ -61,6 +62,7 @@ describe("ProtectedRoute", () => {
6162
refreshToken: "ref",
6263
isAuthenticated: true,
6364
isLoading: false,
65+
authEnabled: true,
6466
login: vi.fn(),
6567
register: vi.fn(),
6668
logout: vi.fn(),
@@ -89,6 +91,7 @@ describe("ProtectedRoute", () => {
8991
refreshToken: null,
9092
isAuthenticated: false,
9193
isLoading: true,
94+
authEnabled: true,
9295
login: vi.fn(),
9396
register: vi.fn(),
9497
logout: vi.fn(),

web/src/test/UserMenu.test.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ describe("UserMenu", () => {
3333
refreshToken: "ref",
3434
isAuthenticated: true,
3535
isLoading: false,
36+
authEnabled: true,
3637
login: vi.fn(),
3738
register: vi.fn(),
3839
logout: mockLogout,
@@ -62,6 +63,7 @@ describe("UserMenu", () => {
6263
refreshToken: "ref",
6364
isAuthenticated: true,
6465
isLoading: false,
66+
authEnabled: true,
6567
login: vi.fn(),
6668
register: vi.fn(),
6769
logout: mockLogout,
@@ -94,6 +96,7 @@ describe("UserMenu", () => {
9496
refreshToken: "ref",
9597
isAuthenticated: true,
9698
isLoading: false,
99+
authEnabled: true,
97100
login: vi.fn(),
98101
register: vi.fn(),
99102
logout: mockLogout,
@@ -120,6 +123,7 @@ describe("UserMenu", () => {
120123
refreshToken: null,
121124
isAuthenticated: false,
122125
isLoading: false,
126+
authEnabled: true,
123127
login: vi.fn(),
124128
register: vi.fn(),
125129
logout: vi.fn(),

0 commit comments

Comments
 (0)