259 lines
6.4 KiB
Rust
259 lines
6.4 KiB
Rust
use std::sync::Arc;
|
|
|
|
use domain::testing::{InMemoryChannelRepository, NoopEventPublisher};
|
|
use domain::value_objects::{ChannelId, UserId};
|
|
use domain::DomainError;
|
|
|
|
use crate::channels::commands::{CreateChannelCommand, UpdateChannelCommand};
|
|
use crate::channels::deps::ChannelCommandDeps;
|
|
use crate::channels::{create, update};
|
|
|
|
fn make_deps() -> (ChannelCommandDeps, Arc<InMemoryChannelRepository>) {
|
|
let repo = Arc::new(InMemoryChannelRepository::new());
|
|
let deps = ChannelCommandDeps {
|
|
channel_command: repo.clone(),
|
|
channel_query: repo.clone(),
|
|
event_publisher: Arc::new(NoopEventPublisher::new()),
|
|
};
|
|
(deps, repo)
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn updates_channel_name() {
|
|
let (deps, _) = make_deps();
|
|
let owner = UserId::generate();
|
|
|
|
let channel = create::execute(
|
|
&deps,
|
|
CreateChannelCommand {
|
|
owner_id: owner,
|
|
name: "Original".into(),
|
|
timezone: "UTC".into(),
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let updated = update::execute(
|
|
&deps,
|
|
UpdateChannelCommand {
|
|
channel_id: channel.id(),
|
|
owner_id: owner,
|
|
name: Some("Renamed".into()),
|
|
description: None,
|
|
timezone: None,
|
|
schedule_config: None,
|
|
rotation_policy: None,
|
|
auto_schedule: None,
|
|
gap_filler: None,
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(updated.name(), "Renamed");
|
|
assert_eq!(updated.timezone(), "UTC"); // unchanged
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn update_fails_if_not_owner() {
|
|
let (deps, _) = make_deps();
|
|
let owner = UserId::generate();
|
|
let stranger = UserId::generate();
|
|
|
|
let channel = create::execute(
|
|
&deps,
|
|
CreateChannelCommand {
|
|
owner_id: owner,
|
|
name: "Protected".into(),
|
|
timezone: "UTC".into(),
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let result = update::execute(
|
|
&deps,
|
|
UpdateChannelCommand {
|
|
channel_id: channel.id(),
|
|
owner_id: stranger,
|
|
name: Some("Hacked".into()),
|
|
description: None,
|
|
timezone: None,
|
|
schedule_config: None,
|
|
rotation_policy: None,
|
|
auto_schedule: None,
|
|
gap_filler: None,
|
|
},
|
|
)
|
|
.await;
|
|
|
|
assert!(result.is_err());
|
|
match result.unwrap_err() {
|
|
DomainError::Forbidden(_) => {}
|
|
other => panic!("expected Forbidden, got: {:?}", other),
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn update_nonexistent_channel_returns_not_found() {
|
|
let (deps, _) = make_deps();
|
|
|
|
let result = update::execute(
|
|
&deps,
|
|
UpdateChannelCommand {
|
|
channel_id: ChannelId::generate(),
|
|
owner_id: UserId::generate(),
|
|
name: Some("Ghost".into()),
|
|
description: None,
|
|
timezone: None,
|
|
schedule_config: None,
|
|
rotation_policy: None,
|
|
auto_schedule: None,
|
|
gap_filler: None,
|
|
},
|
|
)
|
|
.await;
|
|
|
|
assert!(result.is_err());
|
|
assert!(matches!(
|
|
result.unwrap_err(),
|
|
DomainError::ChannelNotFound(_)
|
|
));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn update_config_creates_snapshot() {
|
|
let (deps, repo) = make_deps();
|
|
let owner = UserId::generate();
|
|
|
|
let channel = create::execute(
|
|
&deps,
|
|
CreateChannelCommand {
|
|
owner_id: owner,
|
|
name: "Snapshotted".into(),
|
|
timezone: "UTC".into(),
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Update with new schedule_config
|
|
let new_config = domain::models::ScheduleConfig::default();
|
|
update::execute(
|
|
&deps,
|
|
UpdateChannelCommand {
|
|
channel_id: channel.id(),
|
|
owner_id: owner,
|
|
name: None,
|
|
description: None,
|
|
timezone: None,
|
|
schedule_config: Some(new_config),
|
|
rotation_policy: None,
|
|
auto_schedule: None,
|
|
gap_filler: None,
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Verify a config snapshot was created
|
|
let snapshots = repo.snapshots.lock().unwrap();
|
|
assert_eq!(snapshots.len(), 1);
|
|
assert_eq!(snapshots[0].channel_id(), channel.id());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn update_without_config_skips_snapshot() {
|
|
let (deps, repo) = make_deps();
|
|
let owner = UserId::generate();
|
|
|
|
let channel = create::execute(
|
|
&deps,
|
|
CreateChannelCommand {
|
|
owner_id: owner,
|
|
name: "NoSnapshot".into(),
|
|
timezone: "UTC".into(),
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Update name only — no config change
|
|
update::execute(
|
|
&deps,
|
|
UpdateChannelCommand {
|
|
channel_id: channel.id(),
|
|
owner_id: owner,
|
|
name: Some("Renamed".into()),
|
|
description: None,
|
|
timezone: None,
|
|
schedule_config: None,
|
|
rotation_policy: None,
|
|
auto_schedule: None,
|
|
gap_filler: None,
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
// No snapshot should exist
|
|
let snapshots = repo.snapshots.lock().unwrap();
|
|
assert!(snapshots.is_empty());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn update_description_clear() {
|
|
let (deps, _) = make_deps();
|
|
let owner = UserId::generate();
|
|
|
|
let channel = create::execute(
|
|
&deps,
|
|
CreateChannelCommand {
|
|
owner_id: owner,
|
|
name: "Desc Test".into(),
|
|
timezone: "UTC".into(),
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Set description
|
|
let updated = update::execute(
|
|
&deps,
|
|
UpdateChannelCommand {
|
|
channel_id: channel.id(),
|
|
owner_id: owner,
|
|
name: None,
|
|
description: Some(Some("A description".into())),
|
|
timezone: None,
|
|
schedule_config: None,
|
|
rotation_policy: None,
|
|
auto_schedule: None,
|
|
gap_filler: None,
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(updated.description(), Some("A description"));
|
|
|
|
// Clear description with Some(None)
|
|
let cleared = update::execute(
|
|
&deps,
|
|
UpdateChannelCommand {
|
|
channel_id: channel.id(),
|
|
owner_id: owner,
|
|
name: None,
|
|
description: Some(None),
|
|
timezone: None,
|
|
schedule_config: None,
|
|
rotation_policy: None,
|
|
auto_schedule: None,
|
|
gap_filler: None,
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
assert!(cleared.description().is_none());
|
|
}
|