diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index fa63712f..a8a1c209 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -41,6 +41,9 @@ jobs: - name: test run: cargo test + - name: test (federation off) + run: cargo test --workspace --no-default-features + # Layering guards. These live in the Makefile and were previously local-only, # so a PR could go green in CI while violating them. - name: guards diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1716751e..d0a39ecd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,6 +41,9 @@ jobs: - name: test run: cargo test + - name: test (federation off) + run: cargo test --workspace --no-default-features + # Layering guards. These live in the Makefile and were previously local-only, # so a PR could go green in CI while violating them. - name: guards diff --git a/Makefile b/Makefile index d5126704..293759fb 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ .DEFAULT_GOAL := check -check: fmt-check clippy test check-appcontext check-handler-deps check-handler-repos check-presentation-adapters check-federation-off-social check-binaries-are-thin check-sqlite-is-ap-free +check: fmt-check clippy test test-federation-off check-appcontext check-handler-deps check-handler-repos check-presentation-adapters check-federation-off-social check-binaries-are-thin check-sqlite-is-ap-free @echo "✅ All checks passed" check-appcontext: @@ -99,6 +99,9 @@ clippy: test: cargo test --workspace +test-federation-off: + cargo test --workspace --no-default-features + fix: cargo fmt cargo clippy --fix --allow-dirty --allow-staged @@ -126,4 +129,4 @@ dev-spa: spa-dev: @npm --prefix spa run dev -.PHONY: check fmt fmt-check clippy test fix dev dev-stop dev-worker dev-spa spa-dev check-appcontext check-handler-deps check-handler-repos check-presentation-adapters check-federation-off-social check-binaries-are-thin check-sqlite-is-ap-free +.PHONY: check fmt fmt-check clippy test test-federation-off fix dev dev-stop dev-worker dev-spa spa-dev check-appcontext check-handler-deps check-handler-repos check-presentation-adapters check-federation-off-social check-binaries-are-thin check-sqlite-is-ap-free diff --git a/crates/bootstrap/tests/route_smoke_test.rs b/crates/bootstrap/tests/route_smoke_test.rs index 44d49609..de892ab4 100644 --- a/crates/bootstrap/tests/route_smoke_test.rs +++ b/crates/bootstrap/tests/route_smoke_test.rs @@ -26,8 +26,8 @@ const CLASSIC_VISITOR_PAGES: &[(&str, u16)] = &[ ("/logout", 303), ("/tags/drama", 307), ("/users/{id}/watchlist", 200), - ("/wrapups/{id}/2025", 404), - ("/wrapups/global/2025", 404), + ("/wrapups/{id}/2025", 200), + ("/wrapups/global/2025", 200), ("/users/{id}/following-list", 303), ("/users/{id}/followers-list", 303), ("/posters/abc.jpg", 308), @@ -173,7 +173,7 @@ async fn seed() -> Seed { .connect("sqlite::memory:") .await .unwrap(); - let router = bootstrap::build_server_on(&test_config(), pool) + let router = bootstrap::build_server_on(&test_config(), pool.clone()) .await .unwrap(); @@ -231,10 +231,7 @@ async fn seed() -> Seed { ), ) .await; - assert!( - status == 200 || status == 201, - "log review {status}: {body}" - ); + assert_eq!(status, 201, "log review: {body}"); let (status, _, body) = send( &router, @@ -246,10 +243,7 @@ async fn seed() -> Seed { ), ) .await; - assert!( - status == 200 || status == 201, - "create goal {status}: {body}" - ); + assert_eq!(status, 200, "create goal: {body}"); let (status, _, body) = send( &router, @@ -262,18 +256,14 @@ async fn seed() -> Seed { .await; assert_eq!(status, 200, "list movies: {body}"); let movies: serde_json::Value = serde_json::from_str(&body).unwrap(); - let first = movies - .get("movies") - .and_then(|m| m.get(0)) - .or_else(|| movies.get(0)) - .or_else(|| movies.get("items").and_then(|m| m.get(0))) - .unwrap_or_else(|| panic!("no movie in {body}")); - let movie_id = first["id"] + let movie_id = movies["items"][0]["id"] .as_str() - .or_else(|| first["movie"]["id"].as_str()) .unwrap_or_else(|| panic!("no movie id in {body}")) .to_string(); + seed_ready_wrapup(&pool, Some(&user_id)).await; + seed_ready_wrapup(&pool, None).await; + Seed { router, user_id, @@ -282,6 +272,35 @@ async fn seed() -> Seed { } } +/// Inserts a READY 2025 wrap-up record (user scope, or global when `user_id` is None), +/// the state the worker leaves behind after generating a report. +async fn seed_ready_wrapup(pool: &sqlx::SqlitePool, user_id: Option<&str>) { + use chrono::NaiveDate; + use domain::models::wrapup::{DateRange, WrapUpScope}; + + let start = NaiveDate::from_ymd_opt(2025, 1, 1).unwrap(); + let end = NaiveDate::from_ymd_opt(2026, 1, 1).unwrap(); + let scope = match user_id { + Some(id) => WrapUpScope::User(id.parse().unwrap()), + None => WrapUpScope::Global, + }; + let report = domain::services::wrapup_analyzer::build_report( + scope, + DateRange::new(start, end).unwrap(), + &[], + ); + sqlx::query( + "INSERT INTO wrap_up_records (id, user_id, start_date, end_date, status, report_json) \ + VALUES (?, ?, '2025-01-01', '2026-01-01', 'ready', ?)", + ) + .bind(uuid::Uuid::new_v4().to_string()) + .bind(user_id) + .bind(serde_json::to_string(&report).unwrap()) + .execute(pool) + .await + .unwrap(); +} + fn expand(seed: &Seed, template: &str) -> String { template .replace("{id}", &seed.user_id) @@ -314,6 +333,16 @@ async fn every_classic_page_renders_for_a_visitor() { #[cfg(not(feature = "federation"))] check_table(&seed, FEDERATION_OFF_PAGES, None).await; + // A dropped wrapup route would also 404, so require the template itself. + for template in ["/wrapups/{id}/2025", "/wrapups/global/2025"] { + let (status, _, body) = send(&seed.router, get(&expand(&seed, template), None)).await; + assert_eq!(status, 200, "{template}"); + assert!( + body.contains("class=\"wu-container\""), + "wrapup template not rendered for {template}" + ); + } + // The owner embeds this widget in an iframe on their personal website. let (status, _, body) = send( &seed.router, @@ -339,16 +368,18 @@ async fn every_classic_page_renders_for_the_signed_in_owner() { #[tokio::test] async fn feeds_and_openapi_are_served() { let seed = seed().await; - for template in [ - "/feed.rss", - "/users/{id}/feed.rss", - "/openapi.json", - "/docs/", - "/scalar", + // `/docs` (what users type) redirects to the Swagger UI at `/docs/`. + for (template, expected) in [ + ("/feed.rss", 200), + ("/users/{id}/feed.rss", 200), + ("/openapi.json", 200), + ("/docs", 303), + ("/docs/", 200), + ("/scalar", 200), ] { let uri = expand(&seed, template); let (status, _, _) = send(&seed.router, get(&uri, None)).await; - assert_eq!(status, 200, "{uri}"); + assert_eq!(status, expected, "{uri}"); } }