using Newtonsoft.Json.Linq; using OF_DL.Enumerations; using OF_DL.Models; using OF_DL.Models.Config; using OF_DL.Models.Downloads; using OF_DL.Services; using Serilog.Core; using Serilog.Events; using ArchivedEntities = OF_DL.Models.Entities.Archived; using MessageEntities = OF_DL.Models.Entities.Messages; using PostEntities = OF_DL.Models.Entities.Posts; using PurchasedEntities = OF_DL.Models.Entities.Purchased; using StreamEntities = OF_DL.Models.Entities.Streams; using UserEntities = OF_DL.Models.Entities.Users; namespace OF_DL.Tests.Services; internal sealed class TempFolder : IDisposable { public TempFolder() { Path = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "ofdl-tests", Guid.NewGuid().ToString("N")); Directory.CreateDirectory(Path); } public string Path { get; } public void Dispose() { try { Directory.Delete(Path, true); } catch { // ignored } } } internal sealed class ProgressRecorder : IProgressReporter { public long Total { get; private set; } public void ReportProgress(long increment) => Total += increment; } internal sealed class FakeConfigService(Config config) : IConfigService { public Config CurrentConfig { get; private set; } = config; public bool IsCliNonInteractive { get; } = config.NonInteractiveMode; public Task LoadConfigurationAsync(string[] args) => Task.FromResult(true); public Task SaveConfigurationAsync(string filePath = "config.conf") => Task.CompletedTask; public void UpdateConfig(Config newConfig) => CurrentConfig = newConfig; public List<(string Name, bool Value)> GetToggleableProperties() => []; public bool ApplyToggleableSelections(List selectedNames) => false; } internal sealed class MediaTrackingDbService : IDbService { public bool CheckDownloadedResult { get; init; } public long StoredFileSize { get; init; } public (string folder, long mediaId, string apiType, string directory, string filename, long size, bool downloaded, DateTime createdAt)? LastUpdateMedia { get; private set; } public Task UpdateMedia(string folder, long mediaId, string apiType, string directory, string filename, long size, bool downloaded, DateTime createdAt) { LastUpdateMedia = (folder, mediaId, apiType, directory, filename, size, downloaded, createdAt); return Task.CompletedTask; } public Task GetStoredFileSize(string folder, long mediaId, string apiType) => Task.FromResult(StoredFileSize); public Task CheckDownloaded(string folder, long mediaId, string apiType) => Task.FromResult(CheckDownloadedResult); public Task AddMessage(string folder, long postId, string messageText, string price, bool isPaid, bool isArchived, DateTime createdAt, long userId) => throw new NotImplementedException(); public Task AddPost(string folder, long postId, string messageText, string price, bool isPaid, bool isArchived, DateTime createdAt) => throw new NotImplementedException(); public Task AddStory(string folder, long postId, string messageText, string price, bool isPaid, bool isArchived, DateTime createdAt) => throw new NotImplementedException(); public Task CreateDb(string folder) => throw new NotImplementedException(); public Task CreateUsersDb(Dictionary users) => throw new NotImplementedException(); public Task CheckUsername(KeyValuePair user, string path) => throw new NotImplementedException(); public Task AddMedia(string folder, long mediaId, long postId, string link, string? directory, string? filename, long? size, string apiType, string mediaType, bool preview, bool downloaded, DateTime? createdAt) => throw new NotImplementedException(); public Task GetMostRecentPostDate(string folder) => throw new NotImplementedException(); } internal sealed class StaticApiService : IApiService { public Dictionary? MediaToReturn { get; init; } public DateTime LastModifiedToReturn { get; set; } = new(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc); public bool OfdlCalled { get; private set; } public bool CdmCalled { get; private set; } public Task<(string pssh, DateTime lastModified, double? durationSeconds)> GetDrmMpdInfo( string mpdUrl, string policy, string signature, string kvp) => Task.FromResult<(string pssh, DateTime lastModified, double? durationSeconds)>( ("pssh", LastModifiedToReturn, null)); public Task GetDecryptionKeyOfdl(Dictionary drmHeaders, string licenceUrl, string pssh) { OfdlCalled = true; return Task.FromResult("ofdl-key"); } public Task GetDecryptionKeyCdm(Dictionary drmHeaders, string licenceUrl, string pssh) { CdmCalled = true; return Task.FromResult("cdm-key"); } public Dictionary GetDynamicHeaders(string path, string queryParam) => new() { { "X-Test", "value" } }; public Task?> GetMedia(MediaType mediaType, string endpoint, string? username, string folder) => Task.FromResult(MediaToReturn); public Task?> GetLists(string endpoint) => throw new NotImplementedException(); public Task?> GetListUsers(string endpoint) => throw new NotImplementedException(); public Task GetPaidPosts(string endpoint, string folder, string username, List paidPostIds, IStatusReporter statusReporter) => throw new NotImplementedException(); public Task GetPosts(string endpoint, string folder, List paidPostIds, IStatusReporter statusReporter) => throw new NotImplementedException(); public Task GetPost(string endpoint, string folder) => throw new NotImplementedException(); public Task GetStreams(string endpoint, string folder, List paidPostIds, IStatusReporter statusReporter) => throw new NotImplementedException(); public Task GetArchived(string endpoint, string folder, IStatusReporter statusReporter) => throw new NotImplementedException(); public Task GetMessages(string endpoint, string folder, IStatusReporter statusReporter) => throw new NotImplementedException(); public Task GetPaidMessages(string endpoint, string folder, string username, IStatusReporter statusReporter) => throw new NotImplementedException(); public Task GetPaidMessage(string endpoint, string folder) => throw new NotImplementedException(); public Task> GetPurchasedTabUsers(string endpoint, Dictionary users) => throw new NotImplementedException(); public Task> GetPurchasedTab(string endpoint, string folder, Dictionary users) => throw new NotImplementedException(); public Task GetUserInfo(string endpoint) => throw new NotImplementedException(); public Task GetUserInfoById(string endpoint) => throw new NotImplementedException(); public Task?> GetActiveSubscriptions(string endpoint, bool includeRestrictedSubscriptions) => throw new NotImplementedException(); public Task?> GetExpiredSubscriptions(string endpoint, bool includeRestrictedSubscriptions) => throw new NotImplementedException(); } internal sealed class ConfigurableApiService : IApiService { public Func?>>? ActiveSubscriptionsHandler { get; init; } public Func?>>? ExpiredSubscriptionsHandler { get; init; } public Func?>>? ListsHandler { get; init; } public Func?>>? ListUsersHandler { get; init; } public Func?>>? MediaHandler { get; init; } public Func>? PostHandler { get; init; } public Func>? PaidMessageHandler { get; init; } public Func>? UserInfoHandler { get; init; } public Func>? UserInfoByIdHandler { get; init; } public Task?> GetActiveSubscriptions(string endpoint, bool includeRestrictedSubscriptions) => ActiveSubscriptionsHandler?.Invoke(endpoint, includeRestrictedSubscriptions) ?? Task.FromResult?>(null); public Task?> GetExpiredSubscriptions(string endpoint, bool includeRestrictedSubscriptions) => ExpiredSubscriptionsHandler?.Invoke(endpoint, includeRestrictedSubscriptions) ?? Task.FromResult?>(null); public Task?> GetLists(string endpoint) => ListsHandler?.Invoke(endpoint) ?? Task.FromResult?>(null); public Task?> GetListUsers(string endpoint) => ListUsersHandler?.Invoke(endpoint) ?? Task.FromResult?>(null); public Task?> GetMedia(MediaType mediaType, string endpoint, string? username, string folder) => MediaHandler?.Invoke(mediaType, endpoint, username, folder) ?? Task.FromResult?>(null); public Task GetPost(string endpoint, string folder) => PostHandler?.Invoke(endpoint, folder) ?? Task.FromResult(new PostEntities.SinglePostCollection()); public Task GetPaidMessage(string endpoint, string folder) => PaidMessageHandler?.Invoke(endpoint, folder) ?? Task.FromResult(new PurchasedEntities.SinglePaidMessageCollection()); public Task GetUserInfo(string endpoint) => UserInfoHandler?.Invoke(endpoint) ?? Task.FromResult(null); public Task GetUserInfoById(string endpoint) => UserInfoByIdHandler?.Invoke(endpoint) ?? Task.FromResult(null); public Task GetPaidPosts(string endpoint, string folder, string username, List paidPostIds, IStatusReporter statusReporter) => throw new NotImplementedException(); public Task GetPosts(string endpoint, string folder, List paidPostIds, IStatusReporter statusReporter) => throw new NotImplementedException(); public Task GetStreams(string endpoint, string folder, List paidPostIds, IStatusReporter statusReporter) => throw new NotImplementedException(); public Task GetArchived(string endpoint, string folder, IStatusReporter statusReporter) => throw new NotImplementedException(); public Task GetMessages(string endpoint, string folder, IStatusReporter statusReporter) => throw new NotImplementedException(); public Task GetPaidMessages(string endpoint, string folder, string username, IStatusReporter statusReporter) => throw new NotImplementedException(); public Task> GetPurchasedTabUsers(string endpoint, Dictionary users) => throw new NotImplementedException(); public Task> GetPurchasedTab(string endpoint, string folder, Dictionary users) => throw new NotImplementedException(); public Dictionary GetDynamicHeaders(string path, string queryParam) => throw new NotImplementedException(); public Task GetDecryptionKeyCdm(Dictionary drmHeaders, string licenceUrl, string pssh) => throw new NotImplementedException(); public Task<(string pssh, DateTime lastModified, double? durationSeconds)> GetDrmMpdInfo( string mpdUrl, string policy, string signature, string kvp) => throw new NotImplementedException(); public Task GetDecryptionKeyOfdl(Dictionary drmHeaders, string licenceUrl, string pssh) => throw new NotImplementedException(); } internal sealed class OrchestrationDownloadServiceStub : IDownloadService { public bool SinglePostCalled { get; private set; } public bool SinglePaidMessageCalled { get; private set; } public DownloadResult? SinglePostResult { get; init; } public DownloadResult? SinglePaidMessageResult { get; init; } public DownloadResult? StoriesResult { get; init; } public Task CalculateTotalFileSize(List urls) => Task.FromResult((long)urls.Count); public Task ProcessMediaDownload(string folder, long mediaId, string apiType, string url, string path, string serverFileName, string resolvedFileName, string extension, IProgressReporter progressReporter) => throw new NotImplementedException(); public Task<(string decryptionKey, DateTime lastModified, double? mpdDurationSeconds)?> GetDecryptionInfo(string mpdUrl, string policy, string signature, string kvp, string mediaId, string contentId, string drmType, bool clientIdBlobMissing, bool devicePrivateKeyMissing) => throw new NotImplementedException(); public Task DownloadAvatarHeader(string? avatarUrl, string? headerUrl, string folder, string username) => Task.CompletedTask; public Task DownloadHighlights(string username, long userId, string path, HashSet paidPostIds, IProgressReporter progressReporter) => Task.FromResult(new DownloadResult()); public Task DownloadStories(string username, long userId, string path, HashSet paidPostIds, IProgressReporter progressReporter) => Task.FromResult(StoriesResult ?? new DownloadResult()); public Task DownloadArchived(string username, long userId, string path, Dictionary users, bool clientIdBlobMissing, bool devicePrivateKeyMissing, ArchivedEntities.ArchivedCollection archived, IProgressReporter progressReporter) => throw new NotImplementedException(); public Task DownloadMessages(string username, long userId, string path, Dictionary users, bool clientIdBlobMissing, bool devicePrivateKeyMissing, MessageEntities.MessageCollection messages, IProgressReporter progressReporter) => throw new NotImplementedException(); public Task DownloadPaidMessages(string username, string path, Dictionary users, bool clientIdBlobMissing, bool devicePrivateKeyMissing, PurchasedEntities.PaidMessageCollection paidMessageCollection, IProgressReporter progressReporter) => throw new NotImplementedException(); public Task DownloadStreams(string username, long userId, string path, Dictionary users, bool clientIdBlobMissing, bool devicePrivateKeyMissing, StreamEntities.StreamsCollection streams, IProgressReporter progressReporter) => throw new NotImplementedException(); public Task DownloadFreePosts(string username, long userId, string path, Dictionary users, bool clientIdBlobMissing, bool devicePrivateKeyMissing, PostEntities.PostCollection posts, IProgressReporter progressReporter) => throw new NotImplementedException(); public Task DownloadPaidPosts(string username, long userId, string path, Dictionary users, bool clientIdBlobMissing, bool devicePrivateKeyMissing, PurchasedEntities.PaidPostCollection purchasedPosts, IProgressReporter progressReporter) => throw new NotImplementedException(); public Task DownloadPaidPostsPurchasedTab(string username, string path, Dictionary users, bool clientIdBlobMissing, bool devicePrivateKeyMissing, PurchasedEntities.PaidPostCollection purchasedPosts, IProgressReporter progressReporter) => throw new NotImplementedException(); public Task DownloadPaidMessagesPurchasedTab(string username, string path, Dictionary users, bool clientIdBlobMissing, bool devicePrivateKeyMissing, PurchasedEntities.PaidMessageCollection paidMessageCollection, IProgressReporter progressReporter) => throw new NotImplementedException(); public Task DownloadSinglePost(string username, string path, Dictionary users, bool clientIdBlobMissing, bool devicePrivateKeyMissing, PostEntities.SinglePostCollection post, IProgressReporter progressReporter) { SinglePostCalled = true; return Task.FromResult(SinglePostResult ?? new DownloadResult()); } public Task DownloadSinglePaidMessage(string username, string path, Dictionary users, bool clientIdBlobMissing, bool devicePrivateKeyMissing, PurchasedEntities.SinglePaidMessageCollection singlePaidMessageCollection, IProgressReporter progressReporter) { SinglePaidMessageCalled = true; return Task.FromResult(SinglePaidMessageResult ?? new DownloadResult()); } } internal sealed class UserTrackingDbService : IDbService { public Dictionary? CreatedUsers { get; private set; } public List CreatedDbs { get; } = []; public (KeyValuePair user, string path)? CheckedUser { get; private set; } public Task CreateDb(string folder) { CreatedDbs.Add(folder); return Task.CompletedTask; } public Task CreateUsersDb(Dictionary users) { CreatedUsers = new Dictionary(users); return Task.CompletedTask; } public Task CheckUsername(KeyValuePair user, string path) { CheckedUser = (user, path); return Task.CompletedTask; } public Task AddMessage(string folder, long postId, string messageText, string price, bool isPaid, bool isArchived, DateTime createdAt, long userId) => throw new NotImplementedException(); public Task AddPost(string folder, long postId, string messageText, string price, bool isPaid, bool isArchived, DateTime createdAt) => throw new NotImplementedException(); public Task AddStory(string folder, long postId, string messageText, string price, bool isPaid, bool isArchived, DateTime createdAt) => throw new NotImplementedException(); public Task AddMedia(string folder, long mediaId, long postId, string link, string? directory, string? filename, long? size, string apiType, string mediaType, bool preview, bool downloaded, DateTime? createdAt) => throw new NotImplementedException(); public Task UpdateMedia(string folder, long mediaId, string apiType, string directory, string filename, long size, bool downloaded, DateTime createdAt) => throw new NotImplementedException(); public Task GetStoredFileSize(string folder, long mediaId, string apiType) => throw new NotImplementedException(); public Task CheckDownloaded(string folder, long mediaId, string apiType) => throw new NotImplementedException(); public Task GetMostRecentPostDate(string folder) => throw new NotImplementedException(); } internal sealed class RecordingDownloadEventHandler : IDownloadEventHandler { public List Messages { get; } = []; public List<(string contentType, int mediaCount, int objectCount)> ContentFound { get; } = []; public List NoContent { get; } = []; public List<(string contentType, DownloadResult result)> DownloadCompletes { get; } = []; public List<(string description, long maxValue, bool showSize)> ProgressCalls { get; } = []; public Task WithStatusAsync(string statusMessage, Func> work) => work(new RecordingStatusReporter(statusMessage)); public Task WithProgressAsync(string description, long maxValue, bool showSize, Func> work) { ProgressCalls.Add((description, maxValue, showSize)); return work(new ProgressRecorder()); } public void OnContentFound(string contentType, int mediaCount, int objectCount) => ContentFound.Add((contentType, mediaCount, objectCount)); public void OnNoContentFound(string contentType) => NoContent.Add(contentType); public void OnDownloadComplete(string contentType, DownloadResult result) => DownloadCompletes.Add((contentType, result)); public void OnUserStarting(string username) => Messages.Add($"Starting {username}"); public void OnUserComplete(string username, CreatorDownloadResult result) => Messages.Add($"Completed {username}"); public void OnPurchasedTabUserComplete(string username, int paidPostCount, int paidMessagesCount) => Messages.Add($"Purchased {username}"); public void OnScrapeComplete(TimeSpan elapsed) => Messages.Add("Scrape complete"); public void OnMessage(string message) => Messages.Add(message); } internal sealed class RecordingStatusReporter : IStatusReporter { private readonly List _statuses; public RecordingStatusReporter(string initialStatus) { _statuses = [initialStatus]; } public IReadOnlyList Statuses => _statuses; public void ReportStatus(string message) => _statuses.Add(message); } internal sealed class FakeFileNameService : IFileNameService { public Task BuildFilename(string fileFormat, Dictionary values) => throw new NotImplementedException(); public Task> GetFilename(object info, object media, object author, List selectedProperties, string username, Dictionary? users = null) => throw new NotImplementedException(); } internal sealed class FakeAuthService : IAuthService { public Auth? CurrentAuth { get; set; } public Task LoadFromFileAsync(string filePath = "auth.json") => throw new NotImplementedException(); public Task LoadFromBrowserAsync() => throw new NotImplementedException(); public Task SaveToFileAsync(string filePath = "auth.json") => throw new NotImplementedException(); public void ValidateCookieString() => throw new NotImplementedException(); public Task ValidateAuthAsync() => throw new NotImplementedException(); public void Logout() => throw new NotImplementedException(); } internal sealed class FakeLoggingService : ILoggingService { public LoggingLevelSwitch LevelSwitch { get; } = new(); public LoggingLevel LastLevel { get; private set; } = LoggingLevel.Error; public int UpdateCount { get; private set; } public void UpdateLoggingLevel(LoggingLevel newLevel) { UpdateCount++; LastLevel = newLevel; LevelSwitch.MinimumLevel = (LogEventLevel)newLevel; } public LoggingLevel GetCurrentLoggingLevel() => (LoggingLevel)LevelSwitch.MinimumLevel; }