mod common;
use common::{
TEST_CUTOVER, TEST_MANIFEST_SHA256, TEST_REVISION, activation, actor, apply, drop_database,
envelope, envelope_in, event_count, fresh_sealed_store, fresh_store, maintenance_pool,
raw_store, total_event_count,
};
use gwk_domain::command::KernelCommand;
use gwk_domain::ids::TaskId;
use gwk_domain::protocol::{KernelErrorCode, KernelResult};
use gwk_kernel::store::PgEventStore;
fn task(id: &str) -> KernelCommand {
KernelCommand::CreateTask {
task_id: TaskId::new(id),
kind: None,
title: Some("t".to_owned()),
spec_ref: None,
project: None,
priority: None,
tracker_ref: None,
}
}
fn activate(cutover_id: &str, manifest: &str) -> KernelCommand {
KernelCommand::ActivateKernel {
cutover_id: cutover_id.to_owned(),
archive_manifest_sha256: manifest.to_owned(),
}
}
async fn refuse_envelope(
store: &PgEventStore,
envelope: &gwk_domain::envelope::CommandEnvelope,
) -> (KernelErrorCode, String) {
match store.submit(envelope).await {
KernelResult::Error { code, message, .. } => (code, message),
other => panic!("expected a refusal, got {other:?}"),
}
}
#[tokio::test]
#[ignore = "needs a PostgreSQL; see tests/common/mod.rs"]
async fn genesis_is_exactly_one_event_and_calling_again_appends_nothing() {
let maintenance = maintenance_pool().await;
let (name, store) = raw_store(&maintenance, "genesis", 64).await;
assert_eq!(total_event_count(&store).await, 0);
store.ensure_genesis(TEST_REVISION).await.expect("genesis");
let row = sqlx::query_as::<_, (String, String, String, i64, Option<String>)>(
"SELECT project_id, aggregate_id, event_type, aggregate_version, idempotency_key \
FROM gwk.event WHERE aggregate_type = 'kernel'",
)
.fetch_one(store.pool())
.await
.expect("genesis event");
assert_eq!(
row,
(
"system".to_owned(),
"singleton".to_owned(),
"kernel_initialized".to_owned(),
1,
Some("kernel_initialized:v1".to_owned()),
)
);
let payload: serde_json::Value =
sqlx::query_scalar("SELECT payload FROM gwk.event WHERE aggregate_type = 'kernel'")
.fetch_one(store.pool())
.await
.expect("genesis payload");
assert_eq!(
payload,
serde_json::json!({ "contract_version": 1, "public_revision": TEST_REVISION })
);
assert_eq!(total_event_count(&store).await, 1);
store
.ensure_genesis(TEST_REVISION)
.await
.expect("second genesis is a no-op");
assert_eq!(total_event_count(&store).await, 1);
drop(store);
drop_database(&maintenance, &name).await;
}
#[tokio::test]
#[ignore = "needs a PostgreSQL; see tests/common/mod.rs"]
async fn genesis_refuses_a_revision_it_could_not_later_resolve() {
let maintenance = maintenance_pool().await;
let (name, store) = raw_store(&maintenance, "genesisrev", 64).await;
for bad in ["0123456", "", &"A".repeat(40)] {
let refusal = store
.ensure_genesis(bad)
.await
.expect_err("an unusable revision");
assert_eq!(refusal.code, KernelErrorCode::Validation);
}
assert_eq!(total_event_count(&store).await, 0);
drop(store);
drop_database(&maintenance, &name).await;
}
#[tokio::test]
#[ignore = "needs a PostgreSQL; see tests/common/mod.rs"]
async fn a_kernel_without_an_epoch_admits_nothing_at_all() {
let maintenance = maintenance_pool().await;
let (name, store) = raw_store(&maintenance, "noepoch", 64).await;
let (code, message) = refuse_envelope(&store, &envelope("t", &task("t-1"))).await;
assert_eq!(code, KernelErrorCode::Sealed);
assert!(message.contains("no epoch"), "{message}");
let (code, message) = refuse_envelope(&store, &activation(TEST_CUTOVER)).await;
assert_eq!(code, KernelErrorCode::Sealed);
assert!(message.contains("no epoch"), "{message}");
assert_eq!(total_event_count(&store).await, 0);
drop(store);
drop_database(&maintenance, &name).await;
}
#[tokio::test]
#[ignore = "needs a PostgreSQL; see tests/common/mod.rs"]
async fn a_sealed_kernel_refuses_business_commands_and_admits_activation_alone() {
let maintenance = maintenance_pool().await;
let (name, store) = fresh_sealed_store(&maintenance, "sealed", 64).await;
let (code, message) = refuse_envelope(&store, &envelope("t", &task("t-1"))).await;
assert_eq!(code, KernelErrorCode::Sealed);
assert!(message.contains("create_task"), "{message}");
assert_eq!(event_count(&store).await, 0);
let events = match store.submit(&activation(TEST_CUTOVER)).await {
KernelResult::CommandApplied { events, .. } => events,
other => panic!("expected CommandApplied, got {other:?}"),
};
assert_eq!(events[0].event_type, "kernel_activated");
assert_eq!(events[0].aggregate_version, 2);
apply(&store, "t", task("t-1")).await;
assert_eq!(event_count(&store).await, 1);
drop(store);
drop_database(&maintenance, &name).await;
}
#[tokio::test]
#[ignore = "needs a PostgreSQL; see tests/common/mod.rs"]
async fn the_same_cutover_retries_stably_and_a_different_one_is_refused_by_name() {
let maintenance = maintenance_pool().await;
let (name, store) = fresh_store(&maintenance, "cutover", 64).await;
let first: i64 = total_event_count(&store).await;
match store.submit(&activation(TEST_CUTOVER)).await {
KernelResult::CommandApplied { events, .. } => {
assert_eq!(events[0].aggregate_version, 2);
}
other => panic!("expected the original events, got {other:?}"),
}
assert_eq!(total_event_count(&store).await, first);
let (code, message) = refuse_envelope(&store, &activation("cutover-second")).await;
assert_eq!(code, KernelErrorCode::AlreadyActive);
assert!(message.contains(TEST_CUTOVER), "{message}");
assert!(message.contains("cutover-second"), "{message}");
assert_eq!(total_event_count(&store).await, first);
let mut tampered = activation(TEST_CUTOVER);
let command = activate(TEST_CUTOVER, &"b".repeat(64));
tampered.payload = serde_json::to_value(&command).expect("payload");
let (code, _) = refuse_envelope(&store, &tampered).await;
assert_eq!(code, KernelErrorCode::IdempotencyConflict);
assert_eq!(total_event_count(&store).await, first);
drop(store);
drop_database(&maintenance, &name).await;
}
#[tokio::test]
#[ignore = "needs a PostgreSQL; see tests/common/mod.rs"]
async fn an_activation_must_carry_the_key_and_the_digest_its_cutover_implies() {
let maintenance = maintenance_pool().await;
let (name, store) = fresh_sealed_store(&maintenance, "actshape", 64).await;
let mut wrong_key = activation(TEST_CUTOVER);
wrong_key.idempotency_key = gwk_domain::ids::IdempotencyKey::new("whatever");
let (code, message) = refuse_envelope(&store, &wrong_key).await;
assert_eq!(code, KernelErrorCode::Validation);
assert!(
message.contains("kernel_activated:cutover-test"),
"{message}"
);
let mut bad_digest = activation(TEST_CUTOVER);
let command = activate(TEST_CUTOVER, "not-a-digest");
bad_digest.payload = serde_json::to_value(&command).expect("payload");
let (code, message) = refuse_envelope(&store, &bad_digest).await;
assert_eq!(code, KernelErrorCode::Validation);
assert!(message.contains("64-hex"), "{message}");
assert_eq!(total_event_count(&store).await, 1);
drop(store);
drop_database(&maintenance, &name).await;
}
#[tokio::test]
#[ignore = "needs a PostgreSQL; see tests/common/mod.rs"]
async fn only_the_project_that_wrote_genesis_may_activate() {
let maintenance = maintenance_pool().await;
let (name, store) = fresh_sealed_store(&maintenance, "actowner", 64).await;
let foreign = envelope_in(
"p",
&format!("kernel_activated:{TEST_CUTOVER}"),
actor("kernel"),
&activate(TEST_CUTOVER, TEST_MANIFEST_SHA256),
);
let (code, message) = refuse_envelope(&store, &foreign).await;
assert_eq!(code, KernelErrorCode::Validation);
assert!(
message.contains("belongs to project \"system\""),
"{message}"
);
assert_eq!(total_event_count(&store).await, 1);
match store.submit(&activation(TEST_CUTOVER)).await {
KernelResult::CommandApplied { .. } => {}
other => panic!("expected CommandApplied, got {other:?}"),
}
drop(store);
drop_database(&maintenance, &name).await;
}