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 b698a8380b9eb6ff24f80199581e38e2660f4088
Parent: 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-cospan
4 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
cospan · schematic version control on atproto built on AT Protocol