refactor(kanban): isolate legacy sync fixture
req: v1_release/002
This commit is contained in:
@@ -50,24 +50,24 @@ encryption, retention, backup, and deployment policy remain host concerns.
|
||||
|
||||
## Slice 5 — local-first multiplayer Kanban milestone
|
||||
|
||||
- [ ] **User value:** the complete north-star app demonstrates SSR-first startup, direct manipulation, offline durability, optimistic projection, reconciliation, and live presence as one comprehensible workflow.
|
||||
- **State:** In progress — the named milestone now performs durable projection/replay/upload with generated Rust/WASM plus the framework sync runtime and loads no example-authored JavaScript; the app binary still exposes the legacy app-authored `/sync.js` demo route, so `v1_release/002` cannot close yet.
|
||||
- [x] **User value:** the complete north-star app demonstrates SSR-first startup, direct manipulation, offline durability, optimistic projection, reconciliation, and live presence as one comprehensible workflow.
|
||||
- **State:** Complete.
|
||||
- **Build:** connect the previous slices in the canonical Kanban example; keep native server-rendered fallback; add presence and server-canonical conflict presentation; exercise deploy fingerprint recovery and accessible online/offline/conflict state.
|
||||
- **Refusals:** no demo-only runtime, hidden app JS, proprietary service, or requirement to load collaboration code for server-first apps.
|
||||
- **Requirements:** `ms/001-003`, `v1_release/001-002`, `accessibility/001-007`, `operations/006`, `performance/006`.
|
||||
- **Proof:** `cargo test -p hemx-wasm --test browser multiplayer_kanban_milestone_journey_recovers_and_converges -- --exact` composes the production Kanban page, generated client-local WASM, framework-owned durable sync runtime, and real sync endpoints in one named browser journey. It proves native no-script movement, keyboard movement, local drag/drop, offline/reload projection replay and upload, concurrent peer mutation and canonical convergence, presence projection, typed rejection recovery, mixed-version reload, and optional-asset isolation. The journey explicitly receives 404 for `/app.js` and `/offline.js` and asserts that its only scripts are generated/client runtime plus framework sync runtime. `cargo test -p hemx-wasm --test browser flat_patch_persists_offline_then_uploads_with_same_operation_identity -- --exact` additionally preserves the legacy flat durable-record upgrade path. This proves the durable milestone path but does not yet close `v1_release/002` while the separate legacy `/sync.js` demo route remains in the app binary.
|
||||
|
||||
Execution cursor: delete or replace the legacy app-authored `/sync.js` demo route with the public framework sync mechanism, preserving its recovery/accessibility proof without bespoke example JavaScript.
|
||||
- **Proof:** `cargo test -p hemx-wasm --test browser multiplayer_kanban_milestone_journey_recovers_and_converges -- --exact` composes the production Kanban page, generated client-local WASM, framework-owned durable sync runtime, and real sync endpoints in one named browser journey. It proves native no-script movement, keyboard movement, local drag/drop, offline/reload projection replay and upload, concurrent peer mutation and canonical convergence, presence projection, typed rejection recovery, mixed-version reload, and optional-asset isolation. The journey receives 404 for `/app.js`, `/offline.js`, `/sync-demo`, and `/sync.js` and asserts that its only scripts are generated/client runtime plus framework sync runtime. The former app-authored sync UI remains reachable only behind the explicit `HEMX_KANBAN_LEGACY_SYNC_FIXTURE=1` test-fixture boundary so its recovery/accessibility regression suite remains available without becoming milestone surface. `cargo test -p hemx-wasm --test browser flat_patch_persists_offline_then_uploads_with_same_operation_identity -- --exact` additionally preserves the legacy flat durable-record upgrade path.
|
||||
|
||||
## Slice 6 — production integration reference
|
||||
|
||||
- [ ] **User value:** adopters can copy a proven boundary for durable storage, auth, transactions, security controls, observability, and restart recovery without hemx owning vendor policy.
|
||||
- **State:** Blocked by the remaining Slice 5 legacy `/sync.js` route.
|
||||
- **State:** Active; Slice 5 is complete.
|
||||
- **Build:** evolve one existing reference app using ordinary integration adapters; add durable app storage, authenticated/authorized allowed and denied mutations, CSRF/origin checks, transaction rollback, bounded input, structured failures, health/readiness, tracing/metrics hooks, and restart/deploy recovery.
|
||||
- **Refusals:** no built-in database/auth provider, compliance claim, telemetry vendor, deployment system, or repository framework.
|
||||
- **Requirements:** `security/001-009`, `operations/001-008`, `v1_release/003`, existing `adapter/*`, `integration/*`, and `diagnostics/*` contracts.
|
||||
- **Proof:** end-to-end test survives process restart and mixed deployment, proves allowed/denied/rolled-back mutations and redacted diagnostics, and maps each framework-owned ASVS-relevant control to a failing/passing case.
|
||||
|
||||
Execution cursor: evolve the existing SaaS reference so one authenticated project mutation is authorized, origin/CSRF checked, transactionally durable across process restart, and proven alongside its denied and rolled-back cases through the public server-first entry point.
|
||||
|
||||
## Slice 7 — v1 compatibility and closure
|
||||
|
||||
- [ ] **User value:** maintainers and adopters receive a reproducible, migration-aware v1 with no known material contradiction and no hidden publication side effect.
|
||||
|
||||
@@ -375,7 +375,7 @@ struct AppShell {
|
||||
}
|
||||
|
||||
#[derive(Hemplate)]
|
||||
struct SyncShell {
|
||||
struct LegacySyncFixture {
|
||||
runtime_src: &'static str,
|
||||
}
|
||||
|
||||
@@ -504,13 +504,20 @@ async fn main() {
|
||||
.route("/events", get(events))
|
||||
.route("/sync/broadcast", get(sync_broadcast))
|
||||
.route("/sync/ack", get(sync_ack))
|
||||
.route("/sync-demo", get(sync_demo))
|
||||
.route("/sync.js", get(sync_js))
|
||||
.route("/sync/context", get(sync_context))
|
||||
.route("/sync/commands", post(sync_command))
|
||||
.route("/sync/snapshot", get(sync_snapshot))
|
||||
.route(runtime_js_path(), get(runtime))
|
||||
.layer(middleware::from_fn(ordinary_handler_timeout));
|
||||
let ordinary_routes = if std::env::var_os("HEMX_KANBAN_LEGACY_SYNC_FIXTURE").as_deref()
|
||||
== Some(std::ffi::OsStr::new("1"))
|
||||
{
|
||||
ordinary_routes
|
||||
.route("/sync-demo", get(legacy_sync_fixture))
|
||||
.route("/sync.js", get(legacy_sync_fixture_js))
|
||||
} else {
|
||||
ordinary_routes
|
||||
};
|
||||
let app = ordinary_routes
|
||||
.merge(Router::new().route("/sync/acknowledgements", get(sync_acknowledgements)))
|
||||
.with_state(state);
|
||||
@@ -589,9 +596,9 @@ async fn runtime() -> impl IntoResponse {
|
||||
runtime_js()
|
||||
}
|
||||
|
||||
async fn sync_demo() -> impl IntoResponse {
|
||||
async fn legacy_sync_fixture() -> impl IntoResponse {
|
||||
PageResponse::full(
|
||||
ui::page(&SyncShell {
|
||||
ui::page(&LegacySyncFixture {
|
||||
runtime_src: runtime_js_path(),
|
||||
})
|
||||
.into_string(),
|
||||
@@ -600,10 +607,10 @@ async fn sync_demo() -> impl IntoResponse {
|
||||
.fingerprint(ui::BUILD_FINGERPRINT)
|
||||
}
|
||||
|
||||
async fn sync_js() -> impl IntoResponse {
|
||||
async fn legacy_sync_fixture_js() -> impl IntoResponse {
|
||||
(
|
||||
[("content-type", "text/javascript; charset=utf-8")],
|
||||
include_str!("../static/sync.js"),
|
||||
include_str!("../tests/fixtures/legacy-sync.js"),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -7,9 +7,15 @@ use thirtyfour::prelude::*;
|
||||
|
||||
const STARTUP_TIMEOUT: Duration = Duration::from_secs(12);
|
||||
|
||||
fn app_command() -> Command {
|
||||
let mut command = Command::new(env!("CARGO_BIN_EXE_hemx-kanban-example"));
|
||||
command.env("HEMX_KANBAN_LEGACY_SYNC_FIXTURE", "1");
|
||||
command
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn server_first_route_does_not_load_optional_client_assets() -> WebDriverResult<()> {
|
||||
// test req: performance/006
|
||||
// test req: performance/006 req: v1_release/002
|
||||
let app_port = available_port();
|
||||
let app_addr = format!("127.0.0.1:{app_port}");
|
||||
let mut app = Command::new(env!("CARGO_BIN_EXE_hemx-kanban-example"));
|
||||
@@ -42,6 +48,7 @@ async fn server_first_route_does_not_load_optional_client_assets() -> WebDriverR
|
||||
clientRoots: document.querySelectorAll('[data-hemx-client-module]').length,
|
||||
serviceWorkers: registrations.length,
|
||||
databases: databases.map((database) => database.name),
|
||||
legacyFixtureStatuses: await Promise.all(['/sync-demo', '/sync.js'].map((path) => fetch(path).then((response) => response.status))),
|
||||
});
|
||||
}).catch((error) => done({ error: String(error) }));
|
||||
"#,
|
||||
@@ -55,6 +62,10 @@ async fn server_first_route_does_not_load_optional_client_assets() -> WebDriverR
|
||||
assert_eq!(loaded["clientRoots"], 0);
|
||||
assert_eq!(loaded["serviceWorkers"], 0);
|
||||
assert_eq!(loaded["databases"].as_array().map(Vec::len), Some(0));
|
||||
assert_eq!(
|
||||
loaded["legacyFixtureStatuses"],
|
||||
serde_json::json!([404, 404])
|
||||
);
|
||||
let scripts = loaded["scripts"].as_array().expect("document scripts");
|
||||
assert_eq!(scripts.len(), 1);
|
||||
let runtime_path = scripts[0]["src"].as_str().expect("runtime script path");
|
||||
@@ -95,7 +106,7 @@ async fn idempotent_server_command_is_acknowledged_after_reconnect() -> WebDrive
|
||||
// test req: sync/008 req: sync/012 req: sync/013
|
||||
let app_port = available_port();
|
||||
let app_addr = format!("127.0.0.1:{app_port}");
|
||||
let mut app = Command::new(env!("CARGO_BIN_EXE_hemx-kanban-example"));
|
||||
let mut app = app_command();
|
||||
app.env("HEMX_KANBAN_ADDR", &app_addr);
|
||||
let _app = TestProcess::start(app, "hemx-kanban", &app_addr, STARTUP_TIMEOUT)
|
||||
.expect("start ready hemx-kanban");
|
||||
@@ -210,7 +221,7 @@ async fn pending_local_command_uploads_with_bounded_retry_and_is_removed_on_ack(
|
||||
// test req: sync/004 req: sync/009 req: sync/010 req: sync/016 req: sync/017
|
||||
let app_port = available_port();
|
||||
let app_addr = format!("127.0.0.1:{app_port}");
|
||||
let mut app = Command::new(env!("CARGO_BIN_EXE_hemx-kanban-example"));
|
||||
let mut app = app_command();
|
||||
app.env("HEMX_KANBAN_ADDR", &app_addr)
|
||||
.env("HEMX_KANBAN_FAIL_FIRST_SYNC", "1");
|
||||
let _app = TestProcess::start(app, "hemx-kanban", &app_addr, STARTUP_TIMEOUT)
|
||||
@@ -377,7 +388,7 @@ async fn account_partition_hides_replay_and_export_until_owner_returns() -> WebD
|
||||
// test req: sync/020 req: security/004 req: auth/005 req: operations/002
|
||||
let app_port = available_port();
|
||||
let app_addr = format!("127.0.0.1:{app_port}");
|
||||
let mut app_command = Command::new(env!("CARGO_BIN_EXE_hemx-kanban-example"));
|
||||
let mut app_command = app_command();
|
||||
app_command
|
||||
.env("HEMX_KANBAN_ADDR", &app_addr)
|
||||
.env(
|
||||
@@ -602,7 +613,7 @@ async fn canonical_snapshot_and_history_are_tenant_scoped() -> WebDriverResult<(
|
||||
// test req: sync/007 req: security/004 req: auth/005 req: performance/004
|
||||
let app_port = available_port();
|
||||
let app_addr = format!("127.0.0.1:{app_port}");
|
||||
let mut app_command = Command::new(env!("CARGO_BIN_EXE_hemx-kanban-example"));
|
||||
let mut app_command = app_command();
|
||||
app_command
|
||||
.env("HEMX_KANBAN_ADDR", &app_addr)
|
||||
.env(
|
||||
@@ -725,7 +736,7 @@ async fn schema_upgrade_preserves_queued_order_and_local_intent() -> WebDriverRe
|
||||
// test req: sync/004 req: sync/010 req: sync/014
|
||||
let app_port = available_port();
|
||||
let app_addr = format!("127.0.0.1:{app_port}");
|
||||
let mut app_command = Command::new(env!("CARGO_BIN_EXE_hemx-kanban-example"));
|
||||
let mut app_command = app_command();
|
||||
app_command
|
||||
.env("HEMX_KANBAN_ADDR", &app_addr)
|
||||
.env("HEMX_KANBAN_SYNC_FAILURES", "3");
|
||||
@@ -887,7 +898,7 @@ async fn mixed_queue_removes_accepted_prefix_and_retains_rejected_tail() -> WebD
|
||||
// test req: sync/009 req: sync/010
|
||||
let app_port = available_port();
|
||||
let app_addr = format!("127.0.0.1:{app_port}");
|
||||
let mut app_command = Command::new(env!("CARGO_BIN_EXE_hemx-kanban-example"));
|
||||
let mut app_command = app_command();
|
||||
app_command.env("HEMX_KANBAN_ADDR", &app_addr);
|
||||
let _app = TestProcess::start(app_command, "hemx-kanban", &app_addr, STARTUP_TIMEOUT)
|
||||
.expect("start ready hemx-kanban");
|
||||
@@ -1015,7 +1026,7 @@ async fn upload_backpressure_keeps_pending_work_visible_and_recoverable() -> Web
|
||||
// test req: sync/017
|
||||
let app_port = available_port();
|
||||
let app_addr = format!("127.0.0.1:{app_port}");
|
||||
let mut app_command = Command::new(env!("CARGO_BIN_EXE_hemx-kanban-example"));
|
||||
let mut app_command = app_command();
|
||||
app_command.env("HEMX_KANBAN_ADDR", &app_addr);
|
||||
let _app = TestProcess::start(app_command, "hemx-kanban", &app_addr, STARTUP_TIMEOUT)
|
||||
.expect("start ready hemx-kanban");
|
||||
@@ -1124,7 +1135,7 @@ async fn two_tabs_coordinate_single_uploader_and_takeover_without_duplicate_appl
|
||||
// test req: sync/018
|
||||
let app_port = available_port();
|
||||
let app_addr = format!("127.0.0.1:{app_port}");
|
||||
let mut app_command = Command::new(env!("CARGO_BIN_EXE_hemx-kanban-example"));
|
||||
let mut app_command = app_command();
|
||||
app_command
|
||||
.env("HEMX_KANBAN_ADDR", &app_addr)
|
||||
.env("HEMX_KANBAN_SYNC_FAILURES", "3");
|
||||
@@ -1295,7 +1306,7 @@ async fn exhausted_offline_retries_keep_command_until_later_reconnect() -> WebDr
|
||||
// test req: sync/004 req: sync/010 req: sync/011 req: sync/014 req: sync/016
|
||||
let app_port = available_port();
|
||||
let app_addr = format!("127.0.0.1:{app_port}");
|
||||
let mut app_command = Command::new(env!("CARGO_BIN_EXE_hemx-kanban-example"));
|
||||
let mut app_command = app_command();
|
||||
app_command
|
||||
.env("HEMX_KANBAN_ADDR", &app_addr)
|
||||
.env("HEMX_KANBAN_SYNC_FAILURES", "3");
|
||||
@@ -1421,7 +1432,7 @@ async fn missing_history_rebase_and_user_conflict_resolution_preserve_suffix() -
|
||||
// test req: sync/007 req: sync/010 req: sync/011 req: sync/020
|
||||
let app_port = available_port();
|
||||
let app_addr = format!("127.0.0.1:{app_port}");
|
||||
let mut app_command = Command::new(env!("CARGO_BIN_EXE_hemx-kanban-example"));
|
||||
let mut app_command = app_command();
|
||||
app_command
|
||||
.env("HEMX_KANBAN_ADDR", &app_addr)
|
||||
.env("HEMX_KANBAN_RETAINED_AFTER", "1");
|
||||
@@ -1715,7 +1726,7 @@ async fn redacted_sync_diagnostics_are_bounded_and_leak_no_sensitive_material(
|
||||
// test req: operations/001 req: operations/005 req: security/002 req: sync/016 req: sync/021
|
||||
let app_port = available_port();
|
||||
let app_addr = format!("127.0.0.1:{app_port}");
|
||||
let mut app_command = Command::new(env!("CARGO_BIN_EXE_hemx-kanban-example"));
|
||||
let mut app_command = app_command();
|
||||
app_command
|
||||
.env("HEMX_KANBAN_ADDR", &app_addr)
|
||||
.env("HEMX_KANBAN_SYNC_FAILURES", "3");
|
||||
@@ -1840,7 +1851,7 @@ async fn keep_local_retry_preserves_conflicted_command_and_suffix_order() -> Web
|
||||
// test req: sync/009 req: sync/010 req: sync/011 req: sync/016
|
||||
let app_port = available_port();
|
||||
let app_addr = format!("127.0.0.1:{app_port}");
|
||||
let mut app_command = Command::new(env!("CARGO_BIN_EXE_hemx-kanban-example"));
|
||||
let mut app_command = app_command();
|
||||
app_command
|
||||
.env("HEMX_KANBAN_ADDR", &app_addr)
|
||||
.env("HEMX_KANBAN_RETAINED_AFTER", "1");
|
||||
@@ -2086,7 +2097,7 @@ async fn adversarial_wire_inputs_are_rejected_before_partial_application() -> We
|
||||
// test req: security/005
|
||||
let app_port = available_port();
|
||||
let app_addr = format!("127.0.0.1:{app_port}");
|
||||
let mut app_command = Command::new(env!("CARGO_BIN_EXE_hemx-kanban-example"));
|
||||
let mut app_command = app_command();
|
||||
app_command.env("HEMX_KANBAN_ADDR", &app_addr);
|
||||
let _app = TestProcess::start(app_command, "hemx-kanban", &app_addr, STARTUP_TIMEOUT)
|
||||
.expect("start hemx-kanban");
|
||||
@@ -2206,7 +2217,7 @@ async fn canonical_acknowledgement_updates_generated_atom_over_ordinary_batch(
|
||||
// test req: sync/006
|
||||
let app_port = available_port();
|
||||
let app_addr = format!("127.0.0.1:{app_port}");
|
||||
let mut app_command = Command::new(env!("CARGO_BIN_EXE_hemx-kanban-example"));
|
||||
let mut app_command = app_command();
|
||||
app_command.env("HEMX_KANBAN_ADDR", &app_addr);
|
||||
let _app = TestProcess::start(app_command, "hemx-kanban", &app_addr, STARTUP_TIMEOUT)
|
||||
.expect("start hemx-kanban");
|
||||
@@ -2296,7 +2307,7 @@ async fn typed_presence_join_leave_updates_generated_atom_over_sse() -> WebDrive
|
||||
// test req: sync/001 req: sync/004 req: sync/005
|
||||
let app_port = available_port();
|
||||
let app_addr = format!("127.0.0.1:{app_port}");
|
||||
let mut app_command = Command::new(env!("CARGO_BIN_EXE_hemx-kanban-example"));
|
||||
let mut app_command = app_command();
|
||||
app_command.env("HEMX_KANBAN_ADDR", &app_addr);
|
||||
let _app = TestProcess::start(app_command, "hemx-kanban", &app_addr, STARTUP_TIMEOUT)
|
||||
.expect("start hemx-kanban");
|
||||
@@ -2377,7 +2388,7 @@ async fn ordinary_browser_request_exposes_deadline_and_cancels_on_pagehide() ->
|
||||
// test req: operations/003
|
||||
let app_port = available_port();
|
||||
let app_addr = format!("127.0.0.1:{app_port}");
|
||||
let mut app_command = Command::new(env!("CARGO_BIN_EXE_hemx-kanban-example"));
|
||||
let mut app_command = app_command();
|
||||
app_command.env("HEMX_KANBAN_ADDR", &app_addr);
|
||||
let _app = TestProcess::start(app_command, "hemx-kanban", &app_addr, STARTUP_TIMEOUT)
|
||||
.expect("start hemx-kanban");
|
||||
@@ -2454,7 +2465,7 @@ async fn acknowledgement_stream_bounds_reconnect_buffering_heartbeat_and_cancell
|
||||
// test req: operations/004
|
||||
let app_port = available_port();
|
||||
let app_addr = format!("127.0.0.1:{app_port}");
|
||||
let mut app_command = Command::new(env!("CARGO_BIN_EXE_hemx-kanban-example"));
|
||||
let mut app_command = app_command();
|
||||
app_command
|
||||
.env("HEMX_KANBAN_ADDR", &app_addr)
|
||||
.env("HEMX_KANBAN_ACK_HEARTBEAT_MS", "25");
|
||||
@@ -2560,7 +2571,7 @@ async fn sync_requests_timeout_and_cancel_on_pagehide() -> WebDriverResult<()> {
|
||||
// test req: operations/003
|
||||
let app_port = available_port();
|
||||
let app_addr = format!("127.0.0.1:{app_port}");
|
||||
let mut app_command = Command::new(env!("CARGO_BIN_EXE_hemx-kanban-example"));
|
||||
let mut app_command = app_command();
|
||||
app_command.env("HEMX_KANBAN_ADDR", &app_addr);
|
||||
let _app = TestProcess::start(app_command, "hemx-kanban", &app_addr, STARTUP_TIMEOUT)
|
||||
.expect("start hemx-kanban");
|
||||
@@ -2635,7 +2646,7 @@ async fn identical_sync_inputs_reconcile_deterministically() -> WebDriverResult<
|
||||
// test req: sync/022
|
||||
let app_port = available_port();
|
||||
let app_addr = format!("127.0.0.1:{app_port}");
|
||||
let mut app_command = Command::new(env!("CARGO_BIN_EXE_hemx-kanban-example"));
|
||||
let mut app_command = app_command();
|
||||
app_command.env("HEMX_KANBAN_ADDR", &app_addr);
|
||||
let _app = TestProcess::start(app_command, "hemx-kanban", &app_addr, STARTUP_TIMEOUT)
|
||||
.expect("start hemx-kanban");
|
||||
@@ -2725,7 +2736,7 @@ async fn canonical_acknowledgement_survives_server_restart() -> WebDriverResult<
|
||||
));
|
||||
let _ = fs::remove_file(&store);
|
||||
|
||||
let mut first_app_command = Command::new(env!("CARGO_BIN_EXE_hemx-kanban-example"));
|
||||
let mut first_app_command = app_command();
|
||||
first_app_command
|
||||
.env("HEMX_KANBAN_ADDR", &app_addr)
|
||||
.env("HEMX_KANBAN_SYNC_STORE", &store);
|
||||
@@ -2772,7 +2783,7 @@ async fn canonical_acknowledgement_survives_server_restart() -> WebDriverResult<
|
||||
assert!(persisted.contains("\"tenant\": \"demo\""));
|
||||
|
||||
drop(first_app);
|
||||
let mut second_app_command = Command::new(env!("CARGO_BIN_EXE_hemx-kanban-example"));
|
||||
let mut second_app_command = app_command();
|
||||
second_app_command
|
||||
.env("HEMX_KANBAN_ADDR", &app_addr)
|
||||
.env("HEMX_KANBAN_SYNC_STORE", &store);
|
||||
|
||||
@@ -505,7 +505,7 @@ async fn multiplayer_kanban_milestone_journey_recovers_and_converges() -> WebDri
|
||||
.execute_async(
|
||||
r#"
|
||||
const done = arguments[arguments.length - 1];
|
||||
Promise.all(['/app.js', '/offline.js'].map((path) => fetch(path).then((response) => response.status)))
|
||||
Promise.all(['/app.js', '/offline.js', '/sync-demo', '/sync.js'].map((path) => fetch(path).then((response) => response.status)))
|
||||
.then((statuses) => done({ statuses, scripts: [...document.scripts].map((script) => script.getAttribute('src')) }))
|
||||
.catch((error) => done({ error: String(error) }));
|
||||
"#,
|
||||
@@ -514,7 +514,10 @@ async fn multiplayer_kanban_milestone_journey_recovers_and_converges() -> WebDri
|
||||
.await?
|
||||
.json()
|
||||
.clone();
|
||||
assert_eq!(framework_only["statuses"], serde_json::json!([404, 404]));
|
||||
assert_eq!(
|
||||
framework_only["statuses"],
|
||||
serde_json::json!([404, 404, 404, 404])
|
||||
);
|
||||
assert_eq!(
|
||||
framework_only["scripts"],
|
||||
serde_json::json!(["/hemx.js", "/hemx.client.js", "/hemx-sync.js"])
|
||||
|
||||
Reference in New Issue
Block a user