feat: test infrastructure + fork endpoint tests - docker-compose.test.yml: throwaway Postgres on port 5433 (tmpfs) - scripts/test-db.sh: up/down/status for the test container - pnpm scripts: test:db:up, test:db:down, test, test:appview, test:all - 5 fork endpoint tests: create, custom name, fork count increment, nonexistent source 404, invalid URI 400
Author: Aaron Steven White
Commit
b698a8380b9eb6ff24f80199581e38e2660f4088Parent: 602218228d
Structural diff unavailable
These commits were pushed via plain git push, so no pre-parsed
schemas are available. Install git-remote-cospan and re-push via panproto:// to
see scope-level changes, breaking change detection, and semantic diffs.
brew install panproto/tap/git-remote-cospan4 files changed +164 -0
@@ -207,6 +207,118 @@ async fn search_repos_finds_matching(pool: PgPool) {
207207 assert!(!repos.is_empty(), "search for 'test' should return results"); 208208 } 209209 210+// ─── Fork tests ────────────────────────────────────────────────────── 211+ 212+#[sqlx::test(migrator = "cospan_appview::MIGRATOR")] 213+async fn fork_creates_new_repo(pool: PgPool) { 214+ seed_test_data(&pool).await; 215+ let base = start_server(pool).await; 216+ let client = Client::new(); 217+ 218+ let resp = client 219+ .post(format!("{base}/xrpc/dev.cospan.repo.fork")) 220+ .json(&serde_json::json!({ 221+ "sourceRepo": "at://did:plc:alice/dev.cospan.repo/test-project", 222+ "did": "did:plc:carol", 223+ })) 224+ .send() 225+ .await 226+ .unwrap(); 227+ assert_eq!(resp.status(), 200); 228+ 229+ let json: serde_json::Value = resp.json().await.unwrap(); 230+ assert_eq!(json["did"].as_str().unwrap(), "did:plc:carol"); 231+ assert_eq!(json["name"].as_str().unwrap(), "test-project"); 232+ assert!(json["uri"].as_str().unwrap().starts_with("at://did:plc:carol/dev.cospan.repo/")); 233+ assert_eq!(json["sourceRepo"].as_str().unwrap(), "at://did:plc:alice/dev.cospan.repo/test-project"); 234+} 235+ 236+#[sqlx::test(migrator = "cospan_appview::MIGRATOR")] 237+async fn fork_with_custom_name(pool: PgPool) { 238+ seed_test_data(&pool).await; 239+ let base = start_server(pool).await; 240+ let client = Client::new(); 241+ 242+ let resp = client 243+ .post(format!("{base}/xrpc/dev.cospan.repo.fork")) 244+ .json(&serde_json::json!({ 245+ "sourceRepo": "at://did:plc:alice/dev.cospan.repo/test-project", 246+ "did": "did:plc:carol", 247+ "name": "my-fork", 248+ })) 249+ .send() 250+ .await 251+ .unwrap(); 252+ assert_eq!(resp.status(), 200); 253+ 254+ let json: serde_json::Value = resp.json().await.unwrap(); 255+ assert_eq!(json["name"].as_str().unwrap(), "my-fork"); 256+} 257+ 258+#[sqlx::test(migrator = "cospan_appview::MIGRATOR")] 259+async fn fork_increments_source_fork_count(pool: PgPool) { 260+ seed_test_data(&pool).await; 261+ let base = start_server(pool.clone()).await; 262+ let client = Client::new(); 263+ 264+ // Fork the repo 265+ let resp = client 266+ .post(format!("{base}/xrpc/dev.cospan.repo.fork")) 267+ .json(&serde_json::json!({ 268+ "sourceRepo": "at://did:plc:alice/dev.cospan.repo/test-project", 269+ "did": "did:plc:carol", 270+ })) 271+ .send() 272+ .await 273+ .unwrap(); 274+ assert_eq!(resp.status(), 200); 275+ 276+ // Check source repo's fork_count 277+ let resp = client 278+ .get(format!( 279+ "{base}/xrpc/dev.cospan.repo.get?did=did:plc:alice&name=test-project" 280+ )) 281+ .send() 282+ .await 283+ .unwrap(); 284+ let json: serde_json::Value = resp.json().await.unwrap(); 285+ assert_eq!(json["forkCount"].as_i64().unwrap(), 1); 286+} 287+ 288+#[sqlx::test(migrator = "cospan_appview::MIGRATOR")] 289+async fn fork_nonexistent_source_returns_404(pool: PgPool) { 290+ let base = start_server(pool).await; 291+ let client = Client::new(); 292+ 293+ let resp = client 294+ .post(format!("{base}/xrpc/dev.cospan.repo.fork")) 295+ .json(&serde_json::json!({ 296+ "sourceRepo": "at://did:plc:nobody/dev.cospan.repo/nonexistent", 297+ "did": "did:plc:carol", 298+ })) 299+ .send() 300+ .await 301+ .unwrap(); 302+ assert_eq!(resp.status(), 404); 303+} 304+ 305+#[sqlx::test(migrator = "cospan_appview::MIGRATOR")] 306+async fn fork_invalid_uri_returns_400(pool: PgPool) { 307+ let base = start_server(pool).await; 308+ let client = Client::new(); 309+ 310+ let resp = client 311+ .post(format!("{base}/xrpc/dev.cospan.repo.fork")) 312+ .json(&serde_json::json!({ 313+ "sourceRepo": "not-a-valid-uri", 314+ "did": "did:plc:carol", 315+ })) 316+ .send() 317+ .await 318+ .unwrap(); 319+ assert_eq!(resp.status(), 400); 320+} 321+ 210322 #[sqlx::test(migrator = "cospan_appview::MIGRATOR")] 211323 async fn health_endpoint(pool: PgPool) { 212324 let base = start_server(pool).await;
@@ -0,0 +1,16 @@
1+services: 2+ test-db: 3+ image: postgres:17-alpine 4+ ports: 5+ - "5433:5432" 6+ environment: 7+ POSTGRES_DB: cospan_test 8+ POSTGRES_USER: cospan 9+ POSTGRES_PASSWORD: cospan 10+ tmpfs: 11+ - /var/lib/postgresql/data 12+ healthcheck: 13+ test: ["CMD-SHELL", "pg_isready -U cospan -d cospan_test"] 14+ interval: 2s 15+ timeout: 2s 16+ retries: 10
@@ -3,5 +3,13 @@
33 "private": true, 44 "pnpm": { 55 "onlyBuiltDependencies": ["esbuild"] 6+ }, 7+ "scripts": { 8+ "test:db:up": "./scripts/test-db.sh up", 9+ "test:db:down": "./scripts/test-db.sh down", 10+ "test": "DATABASE_URL=postgres://cospan:cospan@localhost:5433/cospan_test cargo test", 11+ "test:appview": "DATABASE_URL=postgres://cospan:cospan@localhost:5433/cospan_test cargo test -p cospan-appview", 12+ "test:node": "cargo test -p cospan-node", 13+ "test:all": "pnpm test:db:up && pnpm test; EXIT=$?; pnpm test:db:down; exit $EXIT" 614 } 715 }
@@ -0,0 +1,28 @@
1+#!/usr/bin/env bash 2+set -euo pipefail 3+ 4+# Manage the test database container. 5+# Usage: 6+# ./scripts/test-db.sh up # start the test DB 7+# ./scripts/test-db.sh down # stop and remove 8+# ./scripts/test-db.sh status # check if running 9+ 10+COMPOSE_FILE="docker-compose.test.yml" 11+PROJECT="cospan-test" 12+ 13+case "${1:-up}" in 14+ up) 15+ docker compose -f "$COMPOSE_FILE" -p "$PROJECT" up -d --wait 16+ echo "Test DB ready at postgres://cospan:cospan@localhost:5433/cospan_test" 17+ ;; 18+ down) 19+ docker compose -f "$COMPOSE_FILE" -p "$PROJECT" down -v 20+ ;; 21+ status) 22+ docker compose -f "$COMPOSE_FILE" -p "$PROJECT" ps 23+ ;; 24+ *) 25+ echo "Usage: $0 {up|down|status}" >&2 26+ exit 1 27+ ;; 28+esac