Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,22 @@ jobs:
# 连库的测试只有这个 job 跑得动——rust job 没有数据库,那些测试在那边
# 会跳过并显示绿色。不在这里跑一遍,等于写了测试却永远没执行过,
# 比没写更糟:它会让人以为 SQL 有覆盖
# 整套 store 集成测试都在这里跑,而且**没有库就失败**(UTOPIA_TEST_REQUIRE_DB):
# 从前只跑 graph_changes,其余二十几个在没有库的 backend job 里静默跳过,
# 绿色是假的(#248)
- name: 连库测试
run: cargo test -p utopia-store --test graph_changes
run: |
set -o pipefail
cargo test -p utopia-store 2>&1 | tee store-tests.log
env:
UTOPIA_DATABASE_URL: postgres://utopia:utopia@localhost:5432/utopia
UTOPIA_TEST_REQUIRE_DB: "1"
- name: 摘要
if: always()
run: |
passed=$(grep -o '[0-9]* passed' store-tests.log | awk '{s+=$1} END {print s+0}')
failed=$(grep -o '[0-9]* failed' store-tests.log | awk '{s+=$1} END {print s+0}')
echo "utopia-store against Postgres: **${passed} passed**, ${failed} failed — a missing database fails this job instead of skipping" >> "$GITHUB_STEP_SUMMARY"

web:
runs-on: ubuntu-latest
Expand Down
5 changes: 3 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,13 @@ cd web && pnpm install --frozen-lockfile && pnpm build # build type-checks
This is the easiest thing to get wrong here. A green `cargo test --workspace` does not mean everything ran. A number of tests begin like this:

```rust
let Ok(url) = std::env::var("UTOPIA_DATABASE_URL") else {
eprintln!("skipping: UTOPIA_DATABASE_URL not set");
let Some(url) = utopia_store::test_db::url() else {
return Ok(());
};
```

`test_db::url()` reads `UTOPIA_DATABASE_URL`. With `UTOPIA_TEST_REQUIRE_DB=1` also set, a missing database is a failure rather than a skip — that is how the `migrations` job in CI runs the whole `utopia-store` suite, so a green run there means the SQL was exercised. Use the same guard in new tests; do not read the env var directly.

They guard what the compiler cannot see: table aliases inside SQL strings, how `NULL` behaves in a comparison, rows an `INNER JOIN` silently drops, whether a recursive CTE expands the same ancestor twice under diamond inheritance. `cargo check` and clippy say nothing about any of it.

If you touched SQL under `crates/utopia-store/`, set it and run again:
Expand Down
1 change: 1 addition & 0 deletions crates/utopia-store/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,6 @@ pub mod review;
pub mod settings;
pub mod sources;
pub mod temporal;
pub mod test_db;
pub mod tokens;
pub mod workspaces;
29 changes: 29 additions & 0 deletions crates/utopia-store/src/test_db.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//! 集成测试连库的入口(#248)。
//!
//! 每个连库测试都以同一句开头:没有 `UTOPIA_DATABASE_URL` 就跳过而不是失败,
//! 本地随手 `cargo test` 不必先起库。可 CI 上也这么跳,绿色就成了假的:backend job
//! 没有库,24 个 store 集成测试全部静默返回,而有库的 migrations job 只跑了一个。
//!
//! 所以跳过要分场合:设了 `UTOPIA_TEST_REQUIRE_DB` 的地方(CI 的连库 job),
//! 没有库就是失败——「本该跑的没跑」得看得见。

/// 连库测试用的数据库地址。`None` = 这次跳过。
///
/// 设了 `UTOPIA_TEST_REQUIRE_DB` 而没有地址时 panic:这是给 CI 的——那里跳过
/// 等于测试根本没执行,不能显示绿色
pub fn url() -> Option<String> {
match std::env::var("UTOPIA_DATABASE_URL") {
Ok(u) if !u.trim().is_empty() => Some(u),
_ => {
if std::env::var_os("UTOPIA_TEST_REQUIRE_DB").is_some() {
panic!(
"UTOPIA_TEST_REQUIRE_DB is set but UTOPIA_DATABASE_URL is not: this run must not skip database-backed tests"
);
}
eprintln!(
"跳过:未设 UTOPIA_DATABASE_URL(设 UTOPIA_TEST_REQUIRE_DB=1 让跳过变成失败)"
);
None
}
}
}
3 changes: 1 addition & 2 deletions crates/utopia-store/tests/a_contradiction_points_upstream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,7 @@ async fn open_contradictions(

#[tokio::test]
async fn a_contradiction_points_upstream() -> anyhow::Result<()> {
let Ok(url) = std::env::var("UTOPIA_DATABASE_URL") else {
eprintln!("跳过:未设 UTOPIA_DATABASE_URL");
let Some(url) = utopia_store::test_db::url() else {
return Ok(());
};
let pool = PgPool::connect(&url).await?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,7 @@ async fn drift_reviews(

#[tokio::test]
async fn a_declared_disjointness_keeps_names_apart() -> anyhow::Result<()> {
let Ok(url) = std::env::var("UTOPIA_DATABASE_URL") else {
eprintln!("跳过:未设 UTOPIA_DATABASE_URL");
let Some(url) = utopia_store::test_db::url() else {
return Ok(());
};
let pool = PgPool::connect(&url).await?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,7 @@ async fn seed(pool: &PgPool) -> anyhow::Result<Fixture> {

#[tokio::test]
async fn direction_is_judged_by_range_too() -> anyhow::Result<()> {
let Ok(url) = std::env::var("UTOPIA_DATABASE_URL") else {
eprintln!("跳过:未设 UTOPIA_DATABASE_URL");
let Some(url) = utopia_store::test_db::url() else {
return Ok(());
};
let pool = PgPool::connect(&url).await?;
Expand Down
3 changes: 1 addition & 2 deletions crates/utopia-store/tests/a_fact_awaits_a_nod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,7 @@ async fn live_facts(pool: &PgPool, kb: Uuid) -> anyhow::Result<i64> {

#[tokio::test]
async fn a_remembered_fact_waits_for_a_nod() -> anyhow::Result<()> {
let Ok(url) = std::env::var("UTOPIA_DATABASE_URL") else {
eprintln!("跳过:未设 UTOPIA_DATABASE_URL");
let Some(url) = utopia_store::test_db::url() else {
return Ok(());
};
let pool = PgPool::connect(&url).await?;
Expand Down
6 changes: 2 additions & 4 deletions crates/utopia-store/tests/a_mapping_is_not_a_fact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,7 @@ async fn fixture(pool: &PgPool) -> anyhow::Result<(Uuid, Uuid, Uuid)> {

#[tokio::test]
async fn one_concept_one_source_one_mapping() -> anyhow::Result<()> {
let Ok(url) = std::env::var("UTOPIA_DATABASE_URL") else {
eprintln!("跳过:未设 UTOPIA_DATABASE_URL");
let Some(url) = utopia_store::test_db::url() else {
return Ok(());
};
let pool = PgPool::connect(&url).await?;
Expand Down Expand Up @@ -116,8 +115,7 @@ async fn one_concept_one_source_one_mapping() -> anyhow::Result<()> {

#[tokio::test]
async fn a_rejected_mapping_does_not_come_back() -> anyhow::Result<()> {
let Ok(url) = std::env::var("UTOPIA_DATABASE_URL") else {
eprintln!("跳过:未设 UTOPIA_DATABASE_URL");
let Some(url) = utopia_store::test_db::url() else {
return Ok(());
};
let pool = PgPool::connect(&url).await?;
Expand Down
3 changes: 1 addition & 2 deletions crates/utopia-store/tests/a_proof_reaches_the_sentence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,7 @@ async fn asserted(

#[tokio::test]
async fn a_proof_reaches_the_sentence() -> anyhow::Result<()> {
let Ok(url) = std::env::var("UTOPIA_DATABASE_URL") else {
eprintln!("跳过:未设 UTOPIA_DATABASE_URL");
let Some(url) = utopia_store::test_db::url() else {
return Ok(());
};
let pool = PgPool::connect(&url).await?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,7 @@ async fn plain(pool: &PgPool, kb: Uuid, key: &str) -> anyhow::Result<Uuid> {

#[tokio::test]
async fn a_relation_points_only_inside_its_own_kb() -> anyhow::Result<()> {
let Ok(url) = std::env::var("UTOPIA_DATABASE_URL") else {
eprintln!("跳过:未设 UTOPIA_DATABASE_URL");
let Some(url) = utopia_store::test_db::url() else {
return Ok(());
};
let pool = PgPool::connect(&url).await?;
Expand Down
6 changes: 2 additions & 4 deletions crates/utopia-store/tests/a_retired_account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ async fn org_with_two_admins(pool: &PgPool) -> anyhow::Result<(Uuid, Uuid, Uuid)

#[tokio::test]
async fn a_retired_account_cannot_get_back_in() -> anyhow::Result<()> {
let Ok(url) = std::env::var("UTOPIA_DATABASE_URL") else {
eprintln!("跳过:未设 UTOPIA_DATABASE_URL");
let Some(url) = utopia_store::test_db::url() else {
return Ok(());
};
let pool = PgPool::connect(&url).await?;
Expand Down Expand Up @@ -88,8 +87,7 @@ async fn a_retired_account_cannot_get_back_in() -> anyhow::Result<()> {

#[tokio::test]
async fn the_last_admin_and_oneself_are_protected() -> anyhow::Result<()> {
let Ok(url) = std::env::var("UTOPIA_DATABASE_URL") else {
eprintln!("跳过:未设 UTOPIA_DATABASE_URL");
let Some(url) = utopia_store::test_db::url() else {
return Ok(());
};
let pool = PgPool::connect(&url).await?;
Expand Down
3 changes: 1 addition & 2 deletions crates/utopia-store/tests/a_signature_holds_on_every_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,7 @@ async fn open_signature_breaks(pool: &PgPool, kb: Uuid) -> anyhow::Result<Vec<Uu

#[tokio::test]
async fn adoption_and_merge_respect_the_signature() -> anyhow::Result<()> {
let Ok(url) = std::env::var("UTOPIA_DATABASE_URL") else {
eprintln!("跳过:未设 UTOPIA_DATABASE_URL");
let Some(url) = utopia_store::test_db::url() else {
return Ok(());
};
let pool = PgPool::connect(&url).await?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,7 @@ async fn seed(pool: &PgPool) -> anyhow::Result<Fixture> {

#[tokio::test]
async fn a_source_reaches_only_where_it_was_granted() -> anyhow::Result<()> {
let Ok(url) = std::env::var("UTOPIA_DATABASE_URL") else {
eprintln!("跳过:未设 UTOPIA_DATABASE_URL");
let Some(url) = utopia_store::test_db::url() else {
return Ok(());
};
let pool = PgPool::connect(&url).await?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,7 @@ async fn seed(pool: &PgPool) -> anyhow::Result<Fixture> {

#[tokio::test]
async fn a_token_is_the_person_but_not_all_of_them() -> anyhow::Result<()> {
let Ok(url) = std::env::var("UTOPIA_DATABASE_URL") else {
eprintln!("跳过:未设 UTOPIA_DATABASE_URL");
let Some(url) = utopia_store::test_db::url() else {
return Ok(());
};
let pool = PgPool::connect(&url).await?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ async fn seed(pool: &PgPool) -> anyhow::Result<(Uuid, Uuid)> {

#[tokio::test]
async fn a_viewer_never_sees_a_credential() -> anyhow::Result<()> {
let Ok(url) = std::env::var("UTOPIA_DATABASE_URL") else {
eprintln!("跳过:未设 UTOPIA_DATABASE_URL");
let Some(url) = utopia_store::test_db::url() else {
return Ok(());
};
let pool = PgPool::connect(&url).await?;
Expand Down
3 changes: 1 addition & 2 deletions crates/utopia-store/tests/adopt_swap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ use uuid::Uuid;

#[tokio::test]
async fn adopting_a_passive_wording_flips_subject_and_object() -> anyhow::Result<()> {
let Ok(url) = std::env::var("UTOPIA_DATABASE_URL") else {
eprintln!("跳过:未设 UTOPIA_DATABASE_URL");
let Some(url) = utopia_store::test_db::url() else {
return Ok(());
};
let pool = PgPool::connect(&url).await?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ use uuid::Uuid;

#[tokio::test]
async fn adopting_an_iri_turns_the_class_square() -> anyhow::Result<()> {
let Ok(url) = std::env::var("UTOPIA_DATABASE_URL") else {
eprintln!("跳过:未设 UTOPIA_DATABASE_URL");
let Some(url) = utopia_store::test_db::url() else {
return Ok(());
};
let pool = PgPool::connect(&url).await?;
Expand Down
3 changes: 1 addition & 2 deletions crates/utopia-store/tests/axioms_judge_the_ledger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,7 @@ async fn open_kinds(pool: &PgPool, kb: Uuid) -> anyhow::Result<Vec<String>> {

#[tokio::test]
async fn the_ontology_is_the_only_judge() -> anyhow::Result<()> {
let Ok(url) = std::env::var("UTOPIA_DATABASE_URL") else {
eprintln!("跳过:未设 UTOPIA_DATABASE_URL");
let Some(url) = utopia_store::test_db::url() else {
return Ok(());
};
let pool = PgPool::connect(&url).await?;
Expand Down
3 changes: 1 addition & 2 deletions crates/utopia-store/tests/axioms_reach_the_database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ async fn kb(pool: &PgPool) -> anyhow::Result<(Uuid, Uuid)> {

#[tokio::test]
async fn every_axiom_survives_the_bulk_insert() -> anyhow::Result<()> {
let Ok(url) = std::env::var("UTOPIA_DATABASE_URL") else {
eprintln!("跳过:未设 UTOPIA_DATABASE_URL");
let Some(url) = utopia_store::test_db::url() else {
return Ok(());
};
let pool = PgPool::connect(&url).await?;
Expand Down
3 changes: 1 addition & 2 deletions crates/utopia-store/tests/derived_facts_are_second_class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,7 @@ async fn live_derived(pool: &PgPool, kb: Uuid) -> anyhow::Result<Vec<(Uuid, Uuid

#[tokio::test]
async fn what_the_engine_adds_it_can_also_take_back() -> anyhow::Result<()> {
let Ok(url) = std::env::var("UTOPIA_DATABASE_URL") else {
eprintln!("跳过:未设 UTOPIA_DATABASE_URL");
let Some(url) = utopia_store::test_db::url() else {
return Ok(());
};
let pool = PgPool::connect(&url).await?;
Expand Down
6 changes: 2 additions & 4 deletions crates/utopia-store/tests/ended_when_unknown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,7 @@ async fn shape(pool: &PgPool, id: Uuid) -> anyhow::Result<(bool, Option<String>)

#[tokio::test]
async fn a_relation_the_text_says_is_over_is_not_stored_as_ongoing() -> anyhow::Result<()> {
let Ok(url) = std::env::var("UTOPIA_DATABASE_URL") else {
eprintln!("跳过:未设 UTOPIA_DATABASE_URL");
let Some(url) = utopia_store::test_db::url() else {
return Ok(());
};
let pool = PgPool::connect(&url).await?;
Expand Down Expand Up @@ -166,8 +165,7 @@ async fn a_relation_the_text_says_is_over_is_not_stored_as_ongoing() -> anyhow::
/// 一条自己都不知道何时结束的断言,没有资格给别人定结束时刻。
#[tokio::test]
async fn an_already_ended_fact_is_not_treated_as_an_open_claim() -> anyhow::Result<()> {
let Ok(url) = std::env::var("UTOPIA_DATABASE_URL") else {
eprintln!("跳过:未设 UTOPIA_DATABASE_URL");
let Some(url) = utopia_store::test_db::url() else {
return Ok(());
};
let pool = PgPool::connect(&url).await?;
Expand Down
3 changes: 1 addition & 2 deletions crates/utopia-store/tests/graph_changes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,7 @@ fn shape(rows: &[utopia_core::models::GraphChange]) -> Vec<String> {

#[tokio::test]
async fn ledger_events_are_derived_as_specified() -> anyhow::Result<()> {
let Ok(url) = std::env::var("UTOPIA_DATABASE_URL") else {
eprintln!("跳过:未设 UTOPIA_DATABASE_URL");
let Some(url) = utopia_store::test_db::url() else {
return Ok(());
};
let pool = PgPool::connect(&url).await?;
Expand Down
15 changes: 5 additions & 10 deletions crates/utopia-store/tests/human_type_decisions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,7 @@ async fn source_of(pool: &PgPool, id: Uuid) -> anyhow::Result<String> {
/// 主战场:取材条件里的「现类还有子类就纳入」会把人拍过板的实体一并捞回来。
#[tokio::test]
async fn type_resolution_leaves_human_decisions_alone() -> anyhow::Result<()> {
let Ok(url) = std::env::var("UTOPIA_DATABASE_URL") else {
eprintln!("跳过:未设 UTOPIA_DATABASE_URL");
let Some(url) = utopia_store::test_db::url() else {
return Ok(());
};
let pool = PgPool::connect(&url).await?;
Expand Down Expand Up @@ -138,8 +137,7 @@ async fn type_resolution_leaves_human_decisions_alone() -> anyhow::Result<()> {
/// 撤销时也就不会遇到它们,不必额外还原 `type_source`。
#[tokio::test]
async fn adopting_a_new_class_does_not_claim_human_typed_entities() -> anyhow::Result<()> {
let Ok(url) = std::env::var("UTOPIA_DATABASE_URL") else {
eprintln!("跳过:未设 UTOPIA_DATABASE_URL");
let Some(url) = utopia_store::test_db::url() else {
return Ok(());
};
let pool = PgPool::connect(&url).await?;
Expand Down Expand Up @@ -190,8 +188,7 @@ async fn adopting_a_new_class_does_not_claim_human_typed_entities() -> anyhow::R
/// 就是没有」,于是下一次抽取会给它安一个类型。
#[tokio::test]
async fn extraction_does_not_fill_in_a_type_a_human_left_empty() -> anyhow::Result<()> {
let Ok(url) = std::env::var("UTOPIA_DATABASE_URL") else {
eprintln!("跳过:未设 UTOPIA_DATABASE_URL");
let Some(url) = utopia_store::test_db::url() else {
return Ok(());
};
let pool = PgPool::connect(&url).await?;
Expand Down Expand Up @@ -270,8 +267,7 @@ async fn extraction_does_not_fill_in_a_type_a_human_left_empty() -> anyhow::Resu
/// 引擎自动裁决的不是。不必为此加新参数——#112 加 actor 时它就已经在那儿了。
#[tokio::test]
async fn who_approved_a_retype_decides_whether_it_is_protected() -> anyhow::Result<()> {
let Ok(url) = std::env::var("UTOPIA_DATABASE_URL") else {
eprintln!("跳过:未设 UTOPIA_DATABASE_URL");
let Some(url) = utopia_store::test_db::url() else {
return Ok(());
};
let pool = PgPool::connect(&url).await?;
Expand Down Expand Up @@ -322,8 +318,7 @@ async fn who_approved_a_retype_decides_whether_it_is_protected() -> anyhow::Resu
/// `ontology.types_resolved` 审计里,后者才该决定 `type_source`。
#[tokio::test]
async fn an_engine_retype_does_not_lock_the_entity_out_of_the_next_round() -> anyhow::Result<()> {
let Ok(url) = std::env::var("UTOPIA_DATABASE_URL") else {
eprintln!("跳过:未设 UTOPIA_DATABASE_URL");
let Some(url) = utopia_store::test_db::url() else {
return Ok(());
};
let pool = PgPool::connect(&url).await?;
Expand Down
3 changes: 1 addition & 2 deletions crates/utopia-store/tests/miss_dismissal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,7 @@ fn count_of(rows: &[utopia_core::models::OntologyMiss], key: &str) -> Option<i32

#[tokio::test]
async fn dismissing_stops_the_suggestion_but_not_the_counting() -> anyhow::Result<()> {
let Ok(url) = std::env::var("UTOPIA_DATABASE_URL") else {
eprintln!("跳过:未设 UTOPIA_DATABASE_URL");
let Some(url) = utopia_store::test_db::url() else {
return Ok(());
};
let pool = PgPool::connect(&url).await?;
Expand Down
3 changes: 1 addition & 2 deletions crates/utopia-store/tests/no_predicate_still_shows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,7 @@ async fn seed(pool: &PgPool) -> anyhow::Result<Fixture> {

#[tokio::test]
async fn a_fact_without_a_predicate_is_still_visible_everywhere() -> anyhow::Result<()> {
let Ok(url) = std::env::var("UTOPIA_DATABASE_URL") else {
eprintln!("跳过:未设 UTOPIA_DATABASE_URL");
let Some(url) = utopia_store::test_db::url() else {
return Ok(());
};
let pool = PgPool::connect(&url).await?;
Expand Down
6 changes: 2 additions & 4 deletions crates/utopia-store/tests/proposal_counts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,7 @@ async fn seed(pool: &PgPool) -> anyhow::Result<Uuid> {
#[tokio::test]
async fn spread_counts_all_evidence_while_rewrite_count_stays_on_the_backlog() -> anyhow::Result<()>
{
let Ok(url) = std::env::var("UTOPIA_DATABASE_URL") else {
eprintln!("跳过:未设 UTOPIA_DATABASE_URL");
let Some(url) = utopia_store::test_db::url() else {
return Ok(());
};
let pool = PgPool::connect(&url).await?;
Expand Down Expand Up @@ -164,8 +163,7 @@ async fn spread_counts_all_evidence_while_rewrite_count_stays_on_the_backlog() -
/// 丢了(`m.kind = relation_type`),clippy 全绿,任务在运行时才炸。
#[tokio::test]
async fn document_ids_come_back_for_each_wording() -> anyhow::Result<()> {
let Ok(url) = std::env::var("UTOPIA_DATABASE_URL") else {
eprintln!("跳过:未设 UTOPIA_DATABASE_URL");
let Some(url) = utopia_store::test_db::url() else {
return Ok(());
};
let pool = PgPool::connect(&url).await?;
Expand Down
Loading
Loading