strip session-added comments
This commit is contained in:
@@ -1,5 +1,3 @@
|
||||
//! Account deletion over HTTP: JSON and classic endpoints on the real wiring.
|
||||
|
||||
mod support;
|
||||
|
||||
use axum::{
|
||||
@@ -253,7 +251,6 @@ async fn classic_admin_self_delete_logs_out_unless_last_admin() {
|
||||
promote(&pool, &alice.user_id).await;
|
||||
let uri = format!("/users/{}/delete", alice.user_id);
|
||||
|
||||
// Sole admin: refused, redirected back with an error, still logged in.
|
||||
let (status, headers, _) = send(&router, classic_post(&uri, &alice.cookie, PASSWORD)).await;
|
||||
assert_eq!(status, 303);
|
||||
assert!(location(&headers).contains("error="), "{headers:?}");
|
||||
@@ -261,7 +258,6 @@ async fn classic_admin_self_delete_logs_out_unless_last_admin() {
|
||||
let (status, _, _) = send(&router, get(&format!("/users/{}", alice.user_id), None)).await;
|
||||
assert_eq!(status, 200);
|
||||
|
||||
// With a second admin the self-delete goes through and ends the session.
|
||||
promote(&pool, &bob.user_id).await;
|
||||
let (status, headers, _) = send(&router, classic_post(&uri, &alice.cookie, PASSWORD)).await;
|
||||
assert_eq!(status, 303);
|
||||
@@ -283,7 +279,6 @@ async fn delete_forms_render_only_where_they_apply() {
|
||||
let (_, _, body) = send(&router, get("/settings/profile", Some(&alice.cookie))).await;
|
||||
assert!(body.contains(own_delete), "owner settings page");
|
||||
|
||||
// A non-admin viewing someone else, and anonymous viewers, see no form.
|
||||
let bob_page = format!("/users/{}", bob.user_id);
|
||||
let (_, _, body) = send(&router, get(&bob_page, None)).await;
|
||||
assert!(!body.contains(&user_delete(&bob.user_id)), "anonymous");
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
//! End to end on the real wiring: a `UserDeleted` event in the outbox makes
|
||||
//! the worker tell followers, purge the account, and leave the tombstone that
|
||||
//! the queued deliveries are signed from.
|
||||
|
||||
mod support;
|
||||
|
||||
use std::time::Duration;
|
||||
@@ -59,8 +55,6 @@ async fn the_worker_purges_a_deleted_account_and_frees_its_email() {
|
||||
assert!(!again.user_id.is_empty(), "the email can register again");
|
||||
}
|
||||
|
||||
/// With a follower, the Delete is enqueued for delivery even though the
|
||||
/// account is already tombstoned, and the keypair outlives the purge.
|
||||
#[cfg(feature = "federation")]
|
||||
#[tokio::test]
|
||||
async fn followers_are_told_before_the_purge_and_the_keypair_survives_it() {
|
||||
@@ -103,7 +97,6 @@ async fn followers_are_told_before_the_purge_and_the_keypair_survives_it() {
|
||||
"SELECT COUNT(*) FROM users WHERE username = 'alice' AND purged_at IS NOT NULL",
|
||||
)
|
||||
.await;
|
||||
// Deliveries are enqueued from a spawned task, so wait for them too.
|
||||
let enqueued = wait_for(
|
||||
&pool,
|
||||
"SELECT COUNT(*) FROM event_queue WHERE event_type = 'FederationDeliveryRequested'",
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
//! The HTTP surface of the production app, built through `bootstrap` on an
|
||||
//! in-memory database with real adapters.
|
||||
|
||||
mod support;
|
||||
|
||||
use axum::{
|
||||
@@ -16,7 +13,6 @@ async fn test_app() -> Router {
|
||||
support::server().await.0
|
||||
}
|
||||
|
||||
/// An app with one registered user; returns its pool, id and bearer token.
|
||||
async fn test_app_with_user() -> (Router, SqlitePool, Uuid, String) {
|
||||
let (app, pool) = support::server().await;
|
||||
let session = support::register(&app, "insights", "insights@example.com").await;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//! The composition root assembles on a fresh database, with every adapter real.
|
||||
|
||||
mod support;
|
||||
|
||||
#[tokio::test]
|
||||
@@ -21,9 +19,6 @@ async fn worker_builds_on_a_fresh_database() {
|
||||
.expect("worker builds");
|
||||
}
|
||||
|
||||
/// Without the federation feature, following another local user still works
|
||||
/// (the request lands as pending for the target):
|
||||
/// the local social ports are wired in place of the ActivityPub ones.
|
||||
#[cfg(not(feature = "federation"))]
|
||||
#[tokio::test]
|
||||
async fn federation_off_build_wires_local_social() {
|
||||
@@ -68,8 +63,6 @@ async fn federation_off_build_wires_local_social() {
|
||||
);
|
||||
}
|
||||
|
||||
/// The server never talks to the bus: whatever `EVENT_BUS_BACKEND` says, a
|
||||
/// change and its event land in the outbox table. No NATS server is reachable here.
|
||||
#[tokio::test]
|
||||
async fn server_publishes_to_the_outbox_even_when_bus_is_nats() {
|
||||
let pool = support::memory_pool().await;
|
||||
@@ -108,8 +101,6 @@ async fn server_publishes_to_the_outbox_even_when_bus_is_nats() {
|
||||
assert_eq!(queued, 1);
|
||||
}
|
||||
|
||||
/// Handler names key the processed-event ledger, so each must be unique. The
|
||||
/// check runs over the real wiring, in both federation builds.
|
||||
#[tokio::test]
|
||||
async fn handler_names_are_unique() {
|
||||
let pool = support::memory_pool().await;
|
||||
@@ -126,8 +117,6 @@ async fn handler_names_are_unique() {
|
||||
);
|
||||
let mut sorted = names.clone();
|
||||
sorted.sort_unstable();
|
||||
// Names key the ledger: a rename re-runs events inside the retention
|
||||
// window, so changing this list must be a deliberate edit.
|
||||
let mut expected = vec![
|
||||
"account_purge",
|
||||
"image_cleanup",
|
||||
@@ -143,7 +132,6 @@ async fn handler_names_are_unique() {
|
||||
expected.extend(["activitypub", "follow_backfill"]);
|
||||
}
|
||||
expected.sort_unstable();
|
||||
// Image conversion ("image_conversion") is disabled in the test config.
|
||||
assert_eq!(sorted, expected);
|
||||
}
|
||||
|
||||
@@ -157,8 +145,6 @@ async fn queued(pool: &sqlx::SqlitePool, event_type: &str) -> i64 {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// A local follow writes both follow rows and its event in one commit, in
|
||||
/// both federation builds.
|
||||
#[tokio::test]
|
||||
async fn a_local_follow_lands_with_its_event_in_the_outbox() {
|
||||
let (router, pool) = support::server().await;
|
||||
@@ -189,8 +175,6 @@ async fn a_local_follow_lands_with_its_event_in_the_outbox() {
|
||||
assert_eq!(queued(&pool, "FollowRequested").await, 1);
|
||||
}
|
||||
|
||||
/// The k_ap path owns its transaction, so the adapter publishes the event
|
||||
/// itself once the k_ap write has succeeded.
|
||||
#[cfg(feature = "federation")]
|
||||
#[tokio::test]
|
||||
async fn a_remote_block_publishes_its_event_after_the_k_ap_write() {
|
||||
@@ -217,9 +201,6 @@ async fn a_remote_block_publishes_its_event_after_the_k_ap_write() {
|
||||
assert_eq!(queued(&pool, "ActorBlocked").await, 1);
|
||||
}
|
||||
|
||||
/// A token outlives the account that owns it, so every authenticated route
|
||||
/// must re-check the account: a deleted user gets exactly what an anonymous
|
||||
/// caller gets.
|
||||
#[tokio::test]
|
||||
async fn a_deleted_users_token_is_rejected() {
|
||||
let (router, pool) = support::server().await;
|
||||
@@ -255,9 +236,6 @@ async fn a_deleted_users_token_is_rejected() {
|
||||
assert_eq!(headers.get("location").unwrap(), "/login");
|
||||
}
|
||||
|
||||
/// A deleted account stays resolvable inside k-ap (to sign its `Delete`), but
|
||||
/// every ActivityPub document for it answers 410, while the classic profile
|
||||
/// page stays 404.
|
||||
#[cfg(feature = "federation")]
|
||||
#[tokio::test]
|
||||
async fn actor_document_for_a_tombstone_is_gone() {
|
||||
@@ -301,8 +279,6 @@ async fn actor_document_for_a_tombstone_is_gone() {
|
||||
assert_eq!(status, 404, "the classic profile page stays 404");
|
||||
}
|
||||
|
||||
/// Remote servers posting to a deleted account's inbox are told it is gone,
|
||||
/// before any signature check.
|
||||
#[cfg(feature = "federation")]
|
||||
#[tokio::test]
|
||||
async fn posting_to_a_tombstones_inbox_is_gone() {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//! Configuration values reach the running app through the composition root.
|
||||
|
||||
mod support;
|
||||
|
||||
use axum::{
|
||||
|
||||
@@ -395,8 +395,6 @@ async fn following_collection_returns_500_on_federation_error() {
|
||||
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
// --- One refusal shape ---
|
||||
|
||||
async fn refusal_body(resp: axum::response::Response) -> serde_json::Value {
|
||||
let bytes = axum::body::to_bytes(resp.into_body(), usize::MAX)
|
||||
.await
|
||||
|
||||
@@ -246,7 +246,6 @@ async fn admin_api_user_rejects_an_unknown_user() {
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
// An account the store does not hold is inactive, so it is unauthenticated.
|
||||
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
@@ -338,7 +337,6 @@ async fn admin_user_rejects_an_unknown_user() {
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
// An account the store does not hold is inactive: back to the login page.
|
||||
assert_eq!(resp.status(), StatusCode::SEE_OTHER);
|
||||
assert_eq!(resp.headers().get("location").unwrap(), "/login");
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//! `AppState` fixtures: `Panic*` stubs everywhere, real prebuilt `deps` built through
|
||||
//! `bootstrap::build_deps`, with per-port overrides injected BEFORE the deps are built.
|
||||
#![allow(dead_code)]
|
||||
|
||||
use application::config::AppSettings;
|
||||
@@ -25,8 +23,6 @@ use domain::{
|
||||
use http_axum::context::AppContext;
|
||||
use std::sync::Arc;
|
||||
|
||||
// --- Local stubs ---
|
||||
|
||||
struct PanicReviewLogger;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
@@ -168,9 +164,6 @@ pub fn test_app_state_with_search_port(
|
||||
build_test_state_from(repos, Arc::new(PanicAuthService))
|
||||
}
|
||||
|
||||
/// Test state whose repositories are adjusted by `adjust` BEFORE `build_deps`
|
||||
/// runs, for tests that need more than one stub swapped in (e.g. a follow graph
|
||||
/// plus the live user that authenticated routes now look up).
|
||||
pub fn test_app_state_with_repos(
|
||||
adjust: impl FnOnce(&mut Repositories),
|
||||
auth_service: Arc<dyn AuthService>,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//! Crash recovery through the real worker dispatch and the real sqlite ledger.
|
||||
|
||||
mod support;
|
||||
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
@@ -56,9 +54,6 @@ impl EventConsumer for Once {
|
||||
}
|
||||
}
|
||||
|
||||
/// A claim abandoned mid-handler blocks redeliveries (deferred, not failed)
|
||||
/// until its lease lapses; then the next delivery takes it over and runs the
|
||||
/// handler once, and later deliveries skip it.
|
||||
#[tokio::test]
|
||||
async fn an_abandoned_claim_defers_deliveries_until_the_lease_lapses_then_runs_once() {
|
||||
let pool = support::memory_pool().await;
|
||||
@@ -68,7 +63,6 @@ async fn an_abandoned_claim_defers_deliveries_until_the_lease_lapses_then_runs_o
|
||||
Duration::from_secs(1),
|
||||
));
|
||||
let id = EventId::new();
|
||||
// The crashed worker: claimed, never completed or released.
|
||||
assert!(matches!(
|
||||
ledger.begin(&id, "crashy").await.unwrap(),
|
||||
Begin::Run(_)
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
//! The relay claims outbox rows with the SQLite queue's own atomic claim and
|
||||
//! settles each row from the sink's verdict, with fakes at the sink boundary.
|
||||
|
||||
mod support;
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
@@ -20,9 +17,7 @@ use uuid::Uuid;
|
||||
#[derive(Default)]
|
||||
struct FakeSink {
|
||||
fail: bool,
|
||||
/// Fail this many calls, then succeed (a bus outage that ends).
|
||||
fail_first: std::sync::atomic::AtomicUsize,
|
||||
/// When set, `relay` signals `started` then takes this long.
|
||||
slow: Option<Duration>,
|
||||
started: Notify,
|
||||
seen: Mutex<Vec<Option<EventId>>>,
|
||||
@@ -133,8 +128,6 @@ async fn relay_marks_rows_done_only_after_publish_succeeds() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn a_failing_bus_never_spends_the_rows_attempts_and_the_row_relays_on_recovery() {
|
||||
// Budget of 2 attempts, 3 consecutive bus failures: were failures counted
|
||||
// as attempts the row would be dead-lettered before the bus recovers.
|
||||
let (pool, publisher, queue) = outbox_with_attempts(2).await;
|
||||
publisher.publish(&event()).await.unwrap();
|
||||
let sink = Arc::new(FakeSink {
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
//! Characterization of the classic HTML routes, feeds and the OpenAPI surface,
|
||||
//! recorded before the HTTP layer moves. Statuses are what the app returns today.
|
||||
|
||||
mod support;
|
||||
|
||||
use axum::{
|
||||
@@ -13,7 +10,6 @@ use support::{get, json_request, send};
|
||||
const EMAIL: &str = "owner@example.com";
|
||||
const USERNAME: &str = "owner";
|
||||
|
||||
/// Public classic pages. `{id}`, `{username}`, `{movie}` are replaced by seeded values.
|
||||
const CLASSIC_VISITOR_PAGES: &[(&str, u16)] = &[
|
||||
("/", 200),
|
||||
("/users", 200),
|
||||
@@ -36,7 +32,6 @@ const CLASSIC_VISITOR_PAGES: &[(&str, u16)] = &[
|
||||
("/health", 200),
|
||||
];
|
||||
|
||||
/// Pages that need the signed-in owner.
|
||||
const CLASSIC_OWNER_PAGES: &[(&str, u16)] = &[
|
||||
("/reviews/new", 200),
|
||||
("/diary/export", 200),
|
||||
@@ -64,12 +59,10 @@ const FEDERATION_VISITOR_PAGES: &[(&str, u16)] = &[
|
||||
const FEDERATION_OWNER_PAGES: &[(&str, u16)] = &[
|
||||
("/users/{id}/followers", 303),
|
||||
("/users/{id}/following", 303),
|
||||
// The registered owner is not an admin.
|
||||
("/admin/blocked-domains", 403),
|
||||
("/social/blocked", 200),
|
||||
];
|
||||
|
||||
/// Without federation the collection URLs permanently redirect to the HTML lists.
|
||||
#[cfg(not(feature = "federation"))]
|
||||
const FEDERATION_OFF_PAGES: &[(&str, u16)] = &[
|
||||
("/users/{id}/followers", 308),
|
||||
@@ -80,11 +73,9 @@ struct Seed {
|
||||
router: Router,
|
||||
user_id: String,
|
||||
movie_id: String,
|
||||
/// Cookie header for the signed-in owner (csrf + token).
|
||||
owner_cookie: String,
|
||||
}
|
||||
|
||||
/// One user (registered through the classic form), one movie with a spoiler review, one goal.
|
||||
async fn seed() -> Seed {
|
||||
let (router, pool) = support::server().await;
|
||||
let session = support::register(&router, USERNAME, EMAIL).await;
|
||||
@@ -149,8 +140,6 @@ 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};
|
||||
@@ -210,7 +199,6 @@ 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}");
|
||||
@@ -220,7 +208,6 @@ async fn every_classic_page_renders_for_a_visitor() {
|
||||
);
|
||||
}
|
||||
|
||||
// The owner embeds this widget in an iframe on their personal website.
|
||||
let (status, _, body) = send(
|
||||
&seed.router,
|
||||
get(&expand(&seed, "/users/{id}?embed=true"), None),
|
||||
@@ -245,7 +232,6 @@ async fn every_classic_page_renders_for_the_signed_in_owner() {
|
||||
#[tokio::test]
|
||||
async fn feeds_and_openapi_are_served() {
|
||||
let seed = seed().await;
|
||||
// `/docs` (what users type) redirects to the Swagger UI at `/docs/`.
|
||||
for (template, expected) in [
|
||||
("/feed.rss", 200),
|
||||
("/users/{id}/feed.rss", 200),
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//! Shared helpers for the bootstrap integration tests: a production app built
|
||||
//! through `bootstrap` on an in-memory database, driven over HTTP.
|
||||
#![allow(dead_code)]
|
||||
|
||||
use axum::{
|
||||
@@ -29,7 +27,6 @@ pub fn test_config() -> config::AppConfig {
|
||||
nats: Default::default(),
|
||||
storage: Default::default(),
|
||||
image_conversion: Default::default(),
|
||||
// Required to build; never called (seeding logs reviews with manual titles).
|
||||
metadata: config::MetadataConfig {
|
||||
tmdb_api_key: Some("test-key".into()),
|
||||
..Default::default()
|
||||
@@ -94,7 +91,6 @@ pub fn json_request(
|
||||
b.body(Body::from(body.to_string())).unwrap()
|
||||
}
|
||||
|
||||
/// A single-connection in-memory pool (every connection would otherwise get its own database).
|
||||
pub async fn memory_pool() -> sqlx::SqlitePool {
|
||||
sqlx::sqlite::SqlitePoolOptions::new()
|
||||
.max_connections(1)
|
||||
@@ -103,7 +99,6 @@ pub async fn memory_pool() -> sqlx::SqlitePool {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// The production server router on a fresh in-memory database, plus its pool.
|
||||
pub async fn server() -> (Router, sqlx::SqlitePool) {
|
||||
let pool = memory_pool().await;
|
||||
let router = bootstrap::build_server_on(&test_config(), pool.clone())
|
||||
@@ -114,14 +109,10 @@ pub async fn server() -> (Router, sqlx::SqlitePool) {
|
||||
|
||||
pub struct Session {
|
||||
pub user_id: String,
|
||||
/// Bearer token for the JSON API.
|
||||
pub token: String,
|
||||
/// Cookie header for the classic UI (csrf + session).
|
||||
pub cookie: String,
|
||||
}
|
||||
|
||||
/// Registers through the classic form (GET for the csrf cookie, then POST with
|
||||
/// the token), then logs in over the JSON API for a bearer token.
|
||||
pub async fn register(router: &Router, username: &str, email: &str) -> Session {
|
||||
let (status, headers, _) = send(router, get("/register", None)).await;
|
||||
assert_eq!(status, 200, "GET /register");
|
||||
|
||||
Reference in New Issue
Block a user