You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
synctv/synctv-core/tests/media_cache_invalidation_te...

327 lines
10 KiB
Rust

//! Media cache invalidation tests
//!
//! Tests that media edits properly broadcast events.
//!
use chrono::Utc;
use std::sync::Arc;
use std::time::Duration;
use synctv_core::{
models::{
Playlist, PlaylistId, Room, RoomId, RoomMember, RoomRole, RoomStatus, SourceProvider, User,
UserId, UserRole, UserStatus,
},
repository::{
MediaRepository, PlaylistRepository, RoomMemberRepository, RoomRepository, UserRepository,
},
service::{
AddMediaRequest, EditMediaRequest, MediaService, NotificationService, PermissionService,
ProvidersManager, RoomEvent,
},
};
use synctv_core_testing::{create_test_pool, ok};
fn make_user(username: &str) -> User {
let now = Utc::now();
User {
id: UserId::new(),
username: username.to_string(),
role: UserRole::User,
avatar_file_reference_id: None,
status: UserStatus::Active,
signup_method: synctv_core::models::SignupMethod::Email,
created_at: now,
updated_at: now,
version: 0,
deleted_at: None,
is_banned: false,
banned_at: None,
banned_by: None,
banned_reason: None,
}
}
struct MediaEditFixture {
room: Room,
owner: User,
playlist: Playlist,
media_service: MediaService,
notification_service: NotificationService,
}
async fn setup_media_edit_fixture(
pool: &sqlx::PgPool,
owner_name: &str,
room_name: &str,
notification_service: NotificationService,
) -> MediaEditFixture {
let user_repo = UserRepository::new(pool.clone());
let room_repo = RoomRepository::new(pool.clone());
let playlist_repo = PlaylistRepository::new(pool.clone());
let media_repo = MediaRepository::new(pool.clone());
let owner = ok(
user_repo.create(&make_user(owner_name)).await,
"media edit owner should be created",
);
let room = ok(
room_repo
.create(&{
let now = Utc::now();
Room {
id: RoomId::new(),
name: room_name.to_string(),
description: String::new(),
cover_file_reference_id: None,
category: None,
labels: Vec::new(),
created_by: owner.id,
status: RoomStatus::Active,
is_banned: false,
is_public: true,
closed_at: None,
created_at: now,
updated_at: now,
deleted_at: None,
version: 0,
last_activity_at: now,
}
})
.await,
"media edit room should be created",
);
let member_repo_setup = RoomMemberRepository::new(pool.clone());
let owner_member = RoomMember {
room_id: room.id,
user_id: owner.id,
role: RoomRole::Creator,
added_permissions: 0,
removed_permissions: 0,
admin_added_permissions: 0,
admin_removed_permissions: 0,
remark_name: String::new(),
display_tag: String::new(),
joined_at: Utc::now(),
version: 0,
};
ok(
member_repo_setup.add(&owner_member).await,
"media edit owner membership should be created",
);
let playlist = ok(
playlist_repo
.create(&{
let now = Utc::now();
Playlist {
id: PlaylistId::new(),
room_id: room.id,
creator_id: Some(owner.id),
browse_access_mode: synctv_core::models::PlaylistBrowseAccessMode::Default,
name: String::new(),
description: String::new(),
cover_file_reference_id: None,
parent_id: None,
position: 0.0,
source_provider: None,
source_config: None,
provider_instance_name: None,
created_at: now,
updated_at: now,
version: 0,
}
})
.await,
"media edit playlist should be created",
);
let member_repo = synctv_core::repository::RoomMemberRepository::new(pool.clone());
let permission_service = ok(
PermissionService::new(member_repo, room_repo.clone(), None, 1000, 300),
"permission service should build",
);
let provider_repo = synctv_core::repository::ProviderInstanceRepository::new(pool.clone());
let provider = synctv_core::models::ProviderInstance {
name: "direct_url".to_string(),
endpoint: "http://localhost:50051".to_string(),
comment: None,
jwt_secret: None,
custom_ca: None,
timeout: "30".to_string(),
tls: false,
insecure_tls: false,
providers: vec![SourceProvider::DirectUrl],
enabled: true,
created_at: Utc::now(),
updated_at: Utc::now(),
};
ok(
provider_repo.create(&provider).await,
"direct_url provider row should be created",
);
let provider_instance_repo =
synctv_core::repository::ProviderInstanceRepository::new(pool.clone());
let remote_provider_manager =
synctv_core::service::RemoteProviderManager::new(Arc::new(provider_instance_repo));
let providers_manager = Arc::new(ok(
ProvidersManager::new(Arc::new(remote_provider_manager)),
"providers manager should build",
));
ok(
providers_manager
.create_provider_with_default_config("direct_url", "direct_url")
.await,
"direct_url provider instance should be registered",
);
let media_service = MediaService::new(
media_repo,
playlist_repo,
permission_service,
providers_manager,
notification_service.clone(),
);
MediaEditFixture {
room,
owner,
playlist,
media_service,
notification_service,
}
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
#[ignore = "Requires Docker"]
async fn test_edit_media_sends_notification() {
let (_container, pool) = create_test_pool().await;
let fixture = setup_media_edit_fixture(
&pool,
"test_owner",
"Test Room",
NotificationService::default(),
)
.await;
let mut rx = fixture.notification_service.subscribe();
let add_req = AddMediaRequest {
playlist_id: Some(fixture.playlist.id),
name: "Test Media".to_string(),
description: String::new(),
source_provider: SourceProvider::DirectUrl,
provider_instance_name: None,
source_config: synctv_core_testing::direct_url_media_source_config(
"https://example.com/video.mp4",
),
};
let media = ok(
fixture
.media_service
.add_media(fixture.room.id, fixture.owner.id, add_req)
.await,
"media should be added before edit",
);
let edit_req = EditMediaRequest {
media_id: media.id,
name: Some("Updated Media".to_string()),
description: None,
playback_proxy_mode: None,
source_config: None,
provider_instance_name: None,
};
let updated_media = ok(
fixture
.media_service
.edit_media(fixture.room.id, fixture.owner.id, edit_req)
.await,
"media should be edited",
);
assert_eq!(updated_media.name, "Updated Media");
let mut found_update = false;
for _ in 0..10 {
let result = tokio::time::timeout(Duration::from_secs(2), rx.recv()).await;
match result {
Ok(Ok((notif_room_id, event))) => {
assert_eq!(notif_room_id, fixture.room.id);
match event {
RoomEvent::MediaUpdated {
media_id, title, ..
} => {
assert_eq!(media_id, media.id);
assert_eq!(title, "Updated Media");
found_update = true;
break;
}
RoomEvent::MediaAdded { .. } => {}
other => std::panic::panic_any(format!(
"unexpected notification event while waiting for MediaUpdated: {other:?}"
)),
}
}
Ok(Err(e)) => std::panic::panic_any(format!("channel error: {e}")),
Err(_) => break,
}
}
assert!(found_update, "Expected to receive MediaUpdated event");
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
#[ignore = "Requires Docker"]
async fn test_edit_media_without_notification_service_succeeds() {
let (_container, pool) = create_test_pool().await;
let fixture = setup_media_edit_fixture(
&pool,
"test_owner2",
"Test Room 2",
NotificationService::default(),
)
.await;
let add_req = AddMediaRequest {
playlist_id: Some(fixture.playlist.id),
name: "Test Media".to_string(),
description: String::new(),
source_provider: SourceProvider::DirectUrl,
provider_instance_name: None,
source_config: synctv_core_testing::direct_url_media_source_config(
"https://example.com/video.mp4",
),
};
let media = ok(
fixture
.media_service
.add_media(fixture.room.id, fixture.owner.id, add_req)
.await,
"media should be added before edit",
);
let edit_req = EditMediaRequest {
media_id: media.id,
name: Some("Updated Media".to_string()),
description: None,
playback_proxy_mode: None,
source_config: None,
provider_instance_name: None,
};
let updated_media = ok(
fixture
.media_service
.edit_media(fixture.room.id, fixture.owner.id, edit_req)
.await,
"media edit should succeed with default notification service",
);
assert_eq!(updated_media.name, "Updated Media");
}