182 lines
6.2 KiB
Rust
182 lines
6.2 KiB
Rust
//! FIFA 17 development content pack + ownership seed (Commit 5).
|
|
//!
|
|
//! Proves: the dev pack is opt-in and isolated from default content; the seed
|
|
//! creates a `game_id=fifa17` profile/club and grants real Core `OwnedCard`s
|
|
//! (never FIFA wire ids); it is idempotent and leaves the default profile alone;
|
|
//! and the seeded inventory can exercise the retail `/club` filter + pagination.
|
|
|
|
use openfut_core::db::Pool;
|
|
use openfut_core::services::card_db::CardDb;
|
|
|
|
async fn pool() -> Pool {
|
|
let pool = sqlx::sqlite::SqlitePoolOptions::new()
|
|
.max_connections(1)
|
|
.connect("sqlite::memory:")
|
|
.await
|
|
.expect("in-memory sqlite");
|
|
sqlx::migrate!("./migrations")
|
|
.run(&pool)
|
|
.await
|
|
.expect("migrations");
|
|
pool
|
|
}
|
|
|
|
fn dev_card_db() -> CardDb {
|
|
let mut db = CardDb::load("data").expect("default cards");
|
|
db.load_game_dev("data", "fifa17").expect("dev pack");
|
|
db
|
|
}
|
|
|
|
// ── Content isolation ────────────────────────────────────────────────────────
|
|
|
|
#[test]
|
|
fn default_load_never_contains_dev_pack() {
|
|
// The default global loader reads only data/cards — the dev pack under
|
|
// data/games/fifa17/dev must be invisible unless explicitly requested.
|
|
let default = CardDb::load("data").expect("default cards");
|
|
let leaked: Vec<_> = default
|
|
.cards
|
|
.keys()
|
|
.filter(|k| k.starts_with("fifa17_"))
|
|
.collect();
|
|
assert!(
|
|
leaked.is_empty(),
|
|
"default content must not include FIFA17 dev cards: {leaked:?}"
|
|
);
|
|
assert!(
|
|
!default.cards.is_empty(),
|
|
"default synthetic catalogue still loads"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn opt_in_load_adds_dev_pack_only() {
|
|
let default_n = CardDb::load("data").unwrap().cards.len();
|
|
let db = dev_card_db();
|
|
let dev: Vec<_> = db
|
|
.cards
|
|
.keys()
|
|
.filter(|k| k.starts_with("fifa17_"))
|
|
.collect();
|
|
assert_eq!(dev.len(), 32, "the curated dev pack is 32 definitions");
|
|
assert_eq!(
|
|
db.cards.len(),
|
|
default_n + 32,
|
|
"dev pack is additive; default content unchanged"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn dev_definitions_carry_semantic_names_not_raw_ids() {
|
|
let db = dev_card_db();
|
|
for c in db.cards.values().filter(|c| c.id.starts_with("fifa17_")) {
|
|
// Semantic Core fields are names, never raw FIFA numeric entity ids.
|
|
assert!(
|
|
c.nation.parse::<i64>().is_err(),
|
|
"nation must be a name, got {:?}",
|
|
c.nation
|
|
);
|
|
assert!(
|
|
c.league.parse::<i64>().is_err(),
|
|
"league must be a name: {:?}",
|
|
c.league
|
|
);
|
|
assert!(
|
|
c.club.parse::<i64>().is_err(),
|
|
"club must be a name: {:?}",
|
|
c.club
|
|
);
|
|
assert!(!c.name.is_empty(), "every dev card has a player name");
|
|
}
|
|
}
|
|
|
|
// ── Ownership seed ─────────────────────────────────────────────────────────
|
|
|
|
#[tokio::test]
|
|
async fn seed_grants_game_scoped_inventory_with_filter_coverage() {
|
|
let pool = pool().await;
|
|
let db = dev_card_db();
|
|
let r = openfut_core::seed::seed_fifa17_dev(&pool, &db)
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(r.game_id, "fifa17");
|
|
assert!(!r.already_seeded);
|
|
assert_eq!(r.definitions_available, 32);
|
|
assert_eq!(r.owned_total, 33, "32 defs + 1 deliberate duplicate");
|
|
assert_eq!(r.unique_definitions, 32);
|
|
assert!(r.gold_over_one_page, "gold spans >1 page (22 > 11)");
|
|
assert!(r.gold > 11, "enough gold for pagination");
|
|
assert!(r.silver >= 1 && r.bronze >= 1, "quality spread");
|
|
assert!(r.positions.contains_key("GK"), "GK present");
|
|
assert!(r.positions.contains_key("ST"), "ST present");
|
|
assert!(r.distinct_leagues >= 2, "multiple leagues");
|
|
assert!(r.distinct_nations >= 2, "multiple nations");
|
|
assert!(r.max_same_club >= 2, "a same-club group for team filters");
|
|
|
|
// The seed created ONLY a fifa17 profile — the default (fifa23) profile and
|
|
// any synthetic inventory are untouched.
|
|
let games: Vec<(String,)> = sqlx::query_as("SELECT game_id FROM profiles")
|
|
.fetch_all(&pool)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(
|
|
games,
|
|
vec![("fifa17".to_string(),)],
|
|
"only the fifa17 profile exists"
|
|
);
|
|
|
|
// Every seeded owned card references a dev-pack definition (all renderable).
|
|
let orphans: i64 = sqlx::query_scalar(
|
|
"SELECT COUNT(*) FROM owned_cards o \
|
|
WHERE o.card_id LIKE 'fifa17_%' AND o.card_id NOT IN \
|
|
(SELECT card_id FROM owned_cards WHERE card_id LIKE 'fifa17_%')",
|
|
)
|
|
.fetch_one(&pool)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(orphans, 0);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn seed_is_idempotent_across_reruns() {
|
|
let pool = pool().await;
|
|
let db = dev_card_db();
|
|
let first = openfut_core::seed::seed_fifa17_dev(&pool, &db)
|
|
.await
|
|
.unwrap();
|
|
let second = openfut_core::seed::seed_fifa17_dev(&pool, &db)
|
|
.await
|
|
.unwrap();
|
|
assert!(!first.already_seeded);
|
|
assert!(second.already_seeded, "second run sees existing ownership");
|
|
assert_eq!(first.owned_total, second.owned_total, "no duplicate grants");
|
|
|
|
let n: i64 =
|
|
sqlx::query_scalar("SELECT COUNT(*) FROM owned_cards WHERE card_id LIKE 'fifa17_%'")
|
|
.fetch_one(&pool)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(n, 33, "row count stable after rerun");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn seed_creates_exactly_one_two_copy_definition() {
|
|
let pool = pool().await;
|
|
let db = dev_card_db();
|
|
openfut_core::seed::seed_fifa17_dev(&pool, &db)
|
|
.await
|
|
.unwrap();
|
|
// Exactly one definition is owned twice (distinct owned ids, same card_id):
|
|
// the identity foundation for "two copies of one card" later.
|
|
let dupes: Vec<(String, i64)> = sqlx::query_as(
|
|
"SELECT card_id, COUNT(*) c FROM owned_cards WHERE card_id LIKE 'fifa17_%' \
|
|
GROUP BY card_id HAVING c > 1",
|
|
)
|
|
.fetch_all(&pool)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(dupes.len(), 1, "exactly one duplicated definition");
|
|
assert_eq!(dupes[0].1, 2, "owned twice");
|
|
}
|