Skip to content

Commit 50cf9c1

Browse files
fix: Test isolation and setup script for GCP deployment
Test Fixes: - Fix TimeControl mocking (use new= instead of side_effect) - Fix Bundle Service DB isolation (tempfile + DB_PATH env var) - Fix Trust graph genesis node tracking (explicit Set) - Fix governance timezone bug (SQLite trigger datetime comparison) - Fix global DB connection reset between tests (autouse fixture) - Fix governance E2E tests (pass as_of to purge_expired_outreach) Setup: - Add comprehensive setup.sh for GCP VM deployment - Supports Ubuntu, CentOS, macOS - Creates systemd services for production - Configures firewall, initializes database Test Results: 290 passed, 5 failed (integration tests requiring services) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent f515e6f commit 50cf9c1

32 files changed

Lines changed: 4797 additions & 302 deletions

File tree

app/database/governance_repository.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -194,17 +194,28 @@ async def get_outreach(self, outreach_id: str) -> Optional[VoteOutreach]:
194194

195195
return self._row_to_outreach(row)
196196

197-
async def purge_expired_outreach(self) -> int:
197+
async def purge_expired_outreach(self, as_of: Optional[datetime] = None) -> int:
198198
"""
199199
Delete expired outreach records (privacy protection).
200200
201+
Args:
202+
as_of: Optional datetime to use for comparison (for testing).
203+
Defaults to current time.
204+
201205
Returns: Number of records deleted
202206
"""
203207
async with aiosqlite.connect(self.db_path) as db:
204-
cursor = await db.execute(
205-
"DELETE FROM vote_outreach WHERE purge_at < ?",
206-
(datetime.now().isoformat(),)
207-
)
208+
if as_of is not None:
209+
# Use provided time for comparison (testing)
210+
cursor = await db.execute(
211+
"DELETE FROM vote_outreach WHERE datetime(replace(purge_at, 'T', ' ')) < datetime(?)",
212+
(as_of.strftime('%Y-%m-%d %H:%M:%S'),)
213+
)
214+
else:
215+
# Use current local time
216+
cursor = await db.execute(
217+
"DELETE FROM vote_outreach WHERE datetime(replace(purge_at, 'T', ' ')) < datetime('now', 'localtime')"
218+
)
208219
await db.commit()
209220
return cursor.rowcount
210221

app/database/migrations/004_governance_silence_weight.sql

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,12 @@ CREATE INDEX IF NOT EXISTS idx_vote_outreach_session
5151

5252
-- Trigger to auto-delete expired outreach records
5353
-- Runs on INSERT to vote_outreach (cleanup old records)
54+
-- Note: Uses datetime() for proper comparison of ISO format timestamps
5455
CREATE TRIGGER IF NOT EXISTS cleanup_expired_outreach
5556
AFTER INSERT ON vote_outreach
5657
BEGIN
5758
DELETE FROM vote_outreach
58-
WHERE purge_at < CURRENT_TIMESTAMP;
59+
WHERE datetime(replace(purge_at, 'T', ' ')) < datetime('now', 'localtime');
5960
END;
6061

6162
-- Note: We deliberately do NOT create tables to track:

app/services/fork_rights_service.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
Business logic for data export and community forking.
44
"""
5+
import os
56
import uuid
67
import sqlite3
78
import json
@@ -167,6 +168,7 @@ def generate_export_file(self, user_id: str) -> str:
167168

168169
# Create export-specific SQLite database
169170
export_path = f"data/exports/{user_id}-{datetime.now(UTC).isoformat()}.db"
171+
os.makedirs(os.path.dirname(export_path), exist_ok=True)
170172
export_conn = sqlite3.connect(export_path)
171173
export_cursor = export_conn.cursor()
172174

app/services/governance_service.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,16 +210,20 @@ async def get_session(self, session_id: str) -> Optional[VoteSession]:
210210
"""Get a specific vote session"""
211211
return await self.repo.get_vote_session(session_id)
212212

213-
async def purge_expired_outreach(self) -> int:
213+
async def purge_expired_outreach(self, as_of: Optional[datetime] = None) -> int:
214214
"""
215215
Delete expired outreach records (privacy protection).
216216
217217
This should be called periodically by a background job.
218218
219+
Args:
220+
as_of: Optional datetime to use for comparison (for testing).
221+
Defaults to current time.
222+
219223
Returns:
220224
Number of records deleted
221225
"""
222-
return await self.repo.purge_expired_outreach()
226+
return await self.repo.purge_expired_outreach(as_of=as_of)
223227

224228
# Helper methods
225229

frontend/src/App.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import { OffersPage } from './pages/OffersPage'
1010
import { NeedsPage } from './pages/NeedsPage'
1111
import { CreateOfferPage } from './pages/CreateOfferPage'
1212
import { CreateNeedPage } from './pages/CreateNeedPage'
13+
import { EditOfferPage } from './pages/EditOfferPage'
14+
import { EditNeedPage } from './pages/EditNeedPage'
1315
import { ExchangesPage } from './pages/ExchangesPage'
1416
import { DiscoveryPage } from './pages/DiscoveryPage'
1517
import { NetworkResourcesPage } from './pages/NetworkResourcesPage'
@@ -90,9 +92,11 @@ function App() {
9092
<Route path="/" element={<HomePage />} />
9193
<Route path="/offers" element={<OffersPage />} />
9294
<Route path="/offers/create" element={<CreateOfferPage />} />
95+
<Route path="/offers/:id/edit" element={<EditOfferPage />} />
9396
<Route path="/listings/create/offer" element={<CreateOfferPage />} />
9497
<Route path="/needs" element={<NeedsPage />} />
9598
<Route path="/needs/create" element={<CreateNeedPage />} />
99+
<Route path="/needs/:id/edit" element={<EditNeedPage />} />
96100
<Route path="/listings/create/need" element={<CreateNeedPage />} />
97101
<Route path="/exchanges" element={<ExchangesPage />} />
98102
<Route path="/discovery" element={<DiscoveryPage />} />

0 commit comments

Comments
 (0)