推进 server-rs DDD 分层与新接口接线
This commit is contained in:
@@ -1,3 +1,136 @@
|
||||
//! 大鱼吃小鱼应用编排过渡落位。
|
||||
//!
|
||||
//! 这里只组合领域规则并返回结果或事件,不直接调用外部图片、视频或存储服务。
|
||||
|
||||
use shared_kernel::normalize_required_string;
|
||||
|
||||
use crate::{
|
||||
BigFishAssetSlotSnapshot, build_asset_coverage,
|
||||
commands::EvaluateBigFishPublishReadinessCommand, domain::BigFishPublishReadiness,
|
||||
errors::BigFishApplicationError, events::BigFishDomainEvent,
|
||||
};
|
||||
|
||||
/// 发布门禁应用结果,供 adapter 持久化快照或转换成 API DTO。
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct EvaluateBigFishPublishReadinessResult {
|
||||
pub readiness: BigFishPublishReadiness,
|
||||
pub events: Vec<BigFishDomainEvent>,
|
||||
}
|
||||
|
||||
/// 评估 Big Fish 作品是否具备发布条件。
|
||||
///
|
||||
/// 规则只依赖草稿和资产槽:草稿必须存在,等级主图、基础动作和背景图
|
||||
/// 必须满足 `build_asset_coverage` 的统一口径。
|
||||
pub fn evaluate_publish_readiness(
|
||||
command: EvaluateBigFishPublishReadinessCommand,
|
||||
asset_slots: &[BigFishAssetSlotSnapshot],
|
||||
) -> Result<EvaluateBigFishPublishReadinessResult, BigFishApplicationError> {
|
||||
let session_id = normalize_required_string(command.session_id)
|
||||
.ok_or(BigFishApplicationError::MissingSessionId)?;
|
||||
let owner_user_id = normalize_required_string(command.owner_user_id)
|
||||
.ok_or(BigFishApplicationError::MissingOwnerUserId)?;
|
||||
let coverage = build_asset_coverage(command.draft.as_ref(), asset_slots);
|
||||
let readiness = BigFishPublishReadiness {
|
||||
session_id: session_id.clone(),
|
||||
owner_user_id: owner_user_id.clone(),
|
||||
publish_ready: coverage.publish_ready,
|
||||
blockers: coverage.blockers.clone(),
|
||||
evaluated_at_micros: command.evaluated_at_micros,
|
||||
};
|
||||
let event = BigFishDomainEvent::PublishReadinessEvaluated {
|
||||
session_id,
|
||||
owner_user_id,
|
||||
publish_ready: readiness.publish_ready,
|
||||
blockers: readiness.blockers.clone(),
|
||||
occurred_at_micros: readiness.evaluated_at_micros,
|
||||
};
|
||||
|
||||
Ok(EvaluateBigFishPublishReadinessResult {
|
||||
readiness,
|
||||
events: vec![event],
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
BigFishAssetKind, build_generated_asset_slot, compile_default_draft, infer_anchor_pack,
|
||||
};
|
||||
|
||||
fn build_command() -> EvaluateBigFishPublishReadinessCommand {
|
||||
EvaluateBigFishPublishReadinessCommand {
|
||||
session_id: "big-fish-session-1".to_string(),
|
||||
owner_user_id: "user-1".to_string(),
|
||||
draft: Some(compile_default_draft(&infer_anchor_pack("机械深海", None))),
|
||||
evaluated_at_micros: 1_713_680_000_000_000,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn evaluate_publish_readiness_reports_blockers_when_assets_missing() {
|
||||
let result = evaluate_publish_readiness(build_command(), &[]).expect("result");
|
||||
|
||||
assert!(!result.readiness.publish_ready);
|
||||
assert!(
|
||||
result
|
||||
.readiness
|
||||
.blockers
|
||||
.iter()
|
||||
.any(|item| item.contains("等级主图"))
|
||||
);
|
||||
assert_eq!(result.events.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn evaluate_publish_readiness_accepts_complete_assets() {
|
||||
let command = build_command();
|
||||
let draft = command.draft.clone().expect("draft");
|
||||
let mut slots = Vec::new();
|
||||
for level in 1..=draft.runtime_params.level_count {
|
||||
slots.push(
|
||||
build_generated_asset_slot(
|
||||
&command.session_id,
|
||||
&draft,
|
||||
BigFishAssetKind::LevelMainImage,
|
||||
Some(level),
|
||||
None,
|
||||
Some(format!("/assets/level-{level}.png")),
|
||||
command.evaluated_at_micros + level as i64,
|
||||
)
|
||||
.expect("main image slot"),
|
||||
);
|
||||
for motion_key in ["idle_float", "move_swim"] {
|
||||
slots.push(
|
||||
build_generated_asset_slot(
|
||||
&command.session_id,
|
||||
&draft,
|
||||
BigFishAssetKind::LevelMotion,
|
||||
Some(level),
|
||||
Some(motion_key.to_string()),
|
||||
Some(format!("/assets/level-{level}-{motion_key}.webm")),
|
||||
command.evaluated_at_micros + 100 + level as i64,
|
||||
)
|
||||
.expect("motion slot"),
|
||||
);
|
||||
}
|
||||
}
|
||||
slots.push(
|
||||
build_generated_asset_slot(
|
||||
&command.session_id,
|
||||
&draft,
|
||||
BigFishAssetKind::StageBackground,
|
||||
None,
|
||||
None,
|
||||
Some("/assets/bg.png".to_string()),
|
||||
command.evaluated_at_micros + 1_000,
|
||||
)
|
||||
.expect("background slot"),
|
||||
);
|
||||
|
||||
let result = evaluate_publish_readiness(command, &slots).expect("result");
|
||||
|
||||
assert!(result.readiness.publish_ready);
|
||||
assert!(result.readiness.blockers.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user