372 lines
16 KiB
C#
372 lines
16 KiB
C#
using OF_DL.Models.Config;
|
|
using OF_DL.Models.Downloads;
|
|
using OF_DL.Models;
|
|
using PostEntities = OF_DL.Models.Entities.Posts;
|
|
using PurchasedEntities = OF_DL.Models.Entities.Purchased;
|
|
using MessageEntities = OF_DL.Models.Entities.Messages;
|
|
using OF_DL.Services;
|
|
|
|
namespace OF_DL.Tests.Services;
|
|
|
|
public class DownloadServiceTests
|
|
{
|
|
[Fact]
|
|
public async Task ProcessMediaDownload_RenamesServerFileAndUpdatesDb_WhenNotDownloadedButServerFileExists()
|
|
{
|
|
using TempFolder temp = new();
|
|
string folder = NormalizeFolder(Path.Combine(temp.Path, "creator"));
|
|
string path = "/Posts/Free";
|
|
string url = "https://example.com/image.jpg";
|
|
string serverFilename = "server";
|
|
string resolvedFilename = "custom";
|
|
string serverFilePath = $"{folder}{path}/{serverFilename}.jpg";
|
|
Directory.CreateDirectory(Path.GetDirectoryName(serverFilePath) ?? throw new InvalidOperationException());
|
|
await File.WriteAllTextAsync(serverFilePath, "abc");
|
|
|
|
MediaTrackingDbService dbService = new() { CheckDownloadedResult = false };
|
|
FakeConfigService configService = new(new Config { ShowScrapeSize = false });
|
|
DownloadService service = CreateService(configService, dbService);
|
|
ProgressRecorder progress = new();
|
|
|
|
bool isNew = await service.ProcessMediaDownload(folder, 1, "Posts", url, path, serverFilename,
|
|
resolvedFilename, ".jpg", progress);
|
|
|
|
string renamedPath = $"{folder}{path}/{resolvedFilename}.jpg";
|
|
Assert.False(isNew);
|
|
Assert.False(File.Exists(serverFilePath));
|
|
Assert.True(File.Exists(renamedPath));
|
|
Assert.NotNull(dbService.LastUpdateMedia);
|
|
Assert.Equal($"{folder}{path}", dbService.LastUpdateMedia.Value.directory);
|
|
Assert.Equal("custom.jpg", dbService.LastUpdateMedia.Value.filename);
|
|
Assert.Equal(new FileInfo(renamedPath).Length, dbService.LastUpdateMedia.Value.size);
|
|
Assert.Equal(1, progress.Total);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ProcessMediaDownload_RenamesExistingFile_WhenDownloadedAndCustomFormatEnabled()
|
|
{
|
|
using TempFolder temp = new();
|
|
string folder = NormalizeFolder(Path.Combine(temp.Path, "creator"));
|
|
string path = "/Posts/Free";
|
|
string url = "https://example.com/image.jpg";
|
|
string serverFilename = "server";
|
|
string resolvedFilename = "custom";
|
|
string serverFilePath = $"{folder}{path}/{serverFilename}.jpg";
|
|
Directory.CreateDirectory(Path.GetDirectoryName(serverFilePath) ?? throw new InvalidOperationException());
|
|
await File.WriteAllTextAsync(serverFilePath, "abc");
|
|
|
|
MediaTrackingDbService dbService = new() { CheckDownloadedResult = true, StoredFileSize = 123 };
|
|
FakeConfigService configService =
|
|
new(new Config { ShowScrapeSize = false, RenameExistingFilesWhenCustomFormatIsSelected = true });
|
|
DownloadService service = CreateService(configService, dbService);
|
|
ProgressRecorder progress = new();
|
|
|
|
bool isNew = await service.ProcessMediaDownload(folder, 1, "Posts", url, path, serverFilename,
|
|
resolvedFilename, ".jpg", progress);
|
|
|
|
string renamedPath = $"{folder}{path}/{resolvedFilename}.jpg";
|
|
Assert.False(isNew);
|
|
Assert.False(File.Exists(serverFilePath));
|
|
Assert.True(File.Exists(renamedPath));
|
|
Assert.NotNull(dbService.LastUpdateMedia);
|
|
Assert.Equal("custom.jpg", dbService.LastUpdateMedia.Value.filename);
|
|
Assert.Equal(123, dbService.LastUpdateMedia.Value.size);
|
|
Assert.Equal(1, progress.Total);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetDecryptionInfo_UsesOfdlWhenCdmMissing()
|
|
{
|
|
StaticApiService apiService = new();
|
|
DownloadService service =
|
|
CreateService(new FakeConfigService(new Config()), new MediaTrackingDbService(), apiService);
|
|
|
|
(string decryptionKey, DateTime lastModified)? result = await service.GetDecryptionInfo(
|
|
"https://example.com/file.mpd", "policy", "signature", "kvp", "1", "2", "post",
|
|
true, false);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.Equal("ofdl-key", result.Value.decryptionKey);
|
|
Assert.Equal(apiService.LastModifiedToReturn, result.Value.lastModified);
|
|
Assert.True(apiService.OfdlCalled);
|
|
Assert.False(apiService.CdmCalled);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetDecryptionInfo_UsesCdmWhenAvailable()
|
|
{
|
|
StaticApiService apiService = new();
|
|
DownloadService service =
|
|
CreateService(new FakeConfigService(new Config()), new MediaTrackingDbService(), apiService);
|
|
|
|
(string decryptionKey, DateTime lastModified)? result = await service.GetDecryptionInfo(
|
|
"https://example.com/file.mpd", "policy", "signature", "kvp", "1", "2", "post",
|
|
false, false);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.Equal("cdm-key", result.Value.decryptionKey);
|
|
Assert.Equal(apiService.LastModifiedToReturn, result.Value.lastModified);
|
|
Assert.True(apiService.CdmCalled);
|
|
Assert.False(apiService.OfdlCalled);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DownloadHighlights_ReturnsZeroWhenNoMedia()
|
|
{
|
|
StaticApiService apiService = new() { MediaToReturn = new Dictionary<long, string>() };
|
|
DownloadService service =
|
|
CreateService(new FakeConfigService(new Config()), new MediaTrackingDbService(), apiService);
|
|
|
|
DownloadResult result = await service.DownloadHighlights("user", 1, "/tmp/creator", new HashSet<long>(),
|
|
new ProgressRecorder());
|
|
|
|
Assert.Equal(0, result.TotalCount);
|
|
Assert.Equal(0, result.NewDownloads);
|
|
Assert.Equal(0, result.ExistingDownloads);
|
|
Assert.Equal("Highlights", result.MediaType);
|
|
Assert.True(result.Success);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DownloadHighlights_CountsExistingWhenAlreadyDownloaded()
|
|
{
|
|
using TempFolder temp = new();
|
|
string folder = NormalizeFolder(Path.Combine(temp.Path, "creator"));
|
|
StaticApiService apiService = new()
|
|
{
|
|
MediaToReturn = new Dictionary<long, string>
|
|
{
|
|
{ 1, "https://example.com/one.jpg" }, { 2, "https://example.com/two.jpg" }
|
|
}
|
|
};
|
|
MediaTrackingDbService dbService = new() { CheckDownloadedResult = true };
|
|
FakeConfigService configService = new(new Config { ShowScrapeSize = false });
|
|
ProgressRecorder progress = new();
|
|
DownloadService service = CreateService(configService, dbService, apiService);
|
|
|
|
DownloadResult result = await service.DownloadHighlights("user", 1, folder, new HashSet<long>(), progress);
|
|
|
|
Assert.Equal(2, result.TotalCount);
|
|
Assert.Equal(0, result.NewDownloads);
|
|
Assert.Equal(2, result.ExistingDownloads);
|
|
Assert.Equal("Highlights", result.MediaType);
|
|
Assert.True(result.Success);
|
|
Assert.Equal(2, progress.Total);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DownloadFreePosts_UsesDefaultFilenameWhenNoGlobalOrCreatorFormatIsDefined()
|
|
{
|
|
using TempFolder temp = new();
|
|
string folder = NormalizeFolder(Path.Combine(temp.Path, "creator"));
|
|
const string serverFilename = "server-name";
|
|
string existingFilePath = $"{folder}/Posts/Free/Images/{serverFilename}.jpg";
|
|
Directory.CreateDirectory(Path.GetDirectoryName(existingFilePath) ?? throw new InvalidOperationException());
|
|
await File.WriteAllTextAsync(existingFilePath, "abc");
|
|
|
|
Config config = new()
|
|
{
|
|
ShowScrapeSize = false,
|
|
PostFileNameFormat = "",
|
|
CreatorConfigs = new Dictionary<string, CreatorConfig>
|
|
{
|
|
["creator"] = new() { PostFileNameFormat = "" }
|
|
}
|
|
};
|
|
|
|
MediaTrackingDbService dbService = new() { CheckDownloadedResult = false };
|
|
DownloadService service = CreateService(new FakeConfigService(config), dbService);
|
|
ProgressRecorder progress = new();
|
|
PostEntities.PostCollection posts = new()
|
|
{
|
|
Posts = new Dictionary<long, string> { { 1, $"https://example.com/{serverFilename}.jpg" } }
|
|
};
|
|
|
|
DownloadResult result = await service.DownloadFreePosts("creator", 1, folder, new Dictionary<string, long>(),
|
|
false, false, posts, progress);
|
|
|
|
Assert.Equal(1, result.TotalCount);
|
|
Assert.Equal(0, result.NewDownloads);
|
|
Assert.Equal(1, result.ExistingDownloads);
|
|
Assert.NotNull(dbService.LastUpdateMedia);
|
|
Assert.Equal($"{serverFilename}.jpg", dbService.LastUpdateMedia.Value.filename);
|
|
Assert.Equal(1, progress.Total);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DownloadFreePosts_UsesGlobalCustomFormatWhenCreatorCustomFormatNotDefined()
|
|
{
|
|
using TempFolder temp = new();
|
|
string folder = NormalizeFolder(Path.Combine(temp.Path, "creator"));
|
|
const string serverFilename = "server-name";
|
|
string existingFilePath = $"{folder}/Posts/Free/Images/{serverFilename}.jpg";
|
|
Directory.CreateDirectory(Path.GetDirectoryName(existingFilePath) ?? throw new InvalidOperationException());
|
|
await File.WriteAllTextAsync(existingFilePath, "abc");
|
|
|
|
Config config = new()
|
|
{
|
|
ShowScrapeSize = false,
|
|
PostFileNameFormat = "global-custom-name",
|
|
CreatorConfigs = new Dictionary<string, CreatorConfig>
|
|
{
|
|
["creator"] = new() { PostFileNameFormat = "" }
|
|
}
|
|
};
|
|
|
|
MediaTrackingDbService dbService = new() { CheckDownloadedResult = false };
|
|
DownloadService service = CreateService(new FakeConfigService(config), dbService,
|
|
fileNameService: new DeterministicFileNameService());
|
|
ProgressRecorder progress = new();
|
|
PostEntities.PostCollection posts = CreatePostCollection(1, serverFilename);
|
|
|
|
DownloadResult result = await service.DownloadFreePosts("creator", 1, folder, new Dictionary<string, long>(),
|
|
false, false, posts, progress);
|
|
|
|
string renamedPath = $"{folder}/Posts/Free/Images/global-custom-name.jpg";
|
|
Assert.Equal(1, result.TotalCount);
|
|
Assert.Equal(0, result.NewDownloads);
|
|
Assert.Equal(1, result.ExistingDownloads);
|
|
Assert.False(File.Exists(existingFilePath));
|
|
Assert.True(File.Exists(renamedPath));
|
|
Assert.NotNull(dbService.LastUpdateMedia);
|
|
Assert.Equal("global-custom-name.jpg", dbService.LastUpdateMedia.Value.filename);
|
|
Assert.Equal(1, progress.Total);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DownloadFreePosts_UsesCreatorCustomFormatWhenGlobalAndCreatorFormatsAreDefined()
|
|
{
|
|
using TempFolder temp = new();
|
|
string folder = NormalizeFolder(Path.Combine(temp.Path, "creator"));
|
|
const string serverFilename = "server-name";
|
|
string existingFilePath = $"{folder}/Posts/Free/Images/{serverFilename}.jpg";
|
|
Directory.CreateDirectory(Path.GetDirectoryName(existingFilePath) ?? throw new InvalidOperationException());
|
|
await File.WriteAllTextAsync(existingFilePath, "abc");
|
|
|
|
Config config = new()
|
|
{
|
|
ShowScrapeSize = false,
|
|
PostFileNameFormat = "global-custom-name",
|
|
CreatorConfigs = new Dictionary<string, CreatorConfig>
|
|
{
|
|
["creator"] = new() { PostFileNameFormat = "creator-custom-name" }
|
|
}
|
|
};
|
|
|
|
MediaTrackingDbService dbService = new() { CheckDownloadedResult = false };
|
|
DownloadService service = CreateService(new FakeConfigService(config), dbService,
|
|
fileNameService: new DeterministicFileNameService());
|
|
ProgressRecorder progress = new();
|
|
PostEntities.PostCollection posts = CreatePostCollection(1, serverFilename);
|
|
|
|
DownloadResult result = await service.DownloadFreePosts("creator", 1, folder, new Dictionary<string, long>(),
|
|
false, false, posts, progress);
|
|
|
|
string renamedPath = $"{folder}/Posts/Free/Images/creator-custom-name.jpg";
|
|
Assert.Equal(1, result.TotalCount);
|
|
Assert.Equal(0, result.NewDownloads);
|
|
Assert.Equal(1, result.ExistingDownloads);
|
|
Assert.False(File.Exists(existingFilePath));
|
|
Assert.True(File.Exists(renamedPath));
|
|
Assert.NotNull(dbService.LastUpdateMedia);
|
|
Assert.Equal("creator-custom-name.jpg", dbService.LastUpdateMedia.Value.filename);
|
|
Assert.Equal(1, progress.Total);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DownloadPaidPosts_AppliesPaidCustomFormatForDrm_WhenAuthorExistsButFromUserMissing()
|
|
{
|
|
using TempFolder temp = new();
|
|
string folder = NormalizeFolder(Path.Combine(temp.Path, "creator"));
|
|
const string customName = "paid-custom-name";
|
|
const string drmBaseFilename = "video-file";
|
|
string basePath = $"{folder}/Posts/Paid/Videos";
|
|
Directory.CreateDirectory(basePath);
|
|
await File.WriteAllTextAsync($"{basePath}/{customName}.mp4", "custom");
|
|
await File.WriteAllTextAsync($"{basePath}/{drmBaseFilename}_source.mp4", "server");
|
|
|
|
Config config = new() { ShowScrapeSize = false, PaidPostFileNameFormat = customName, PostFileNameFormat = "" };
|
|
|
|
MediaTrackingDbService dbService = new() { CheckDownloadedResult = false };
|
|
StaticApiService apiService = new();
|
|
FakeAuthService authService = new()
|
|
{
|
|
CurrentAuth = new Auth { Cookie = "sess=test;", UserAgent = "unit-test-agent" }
|
|
};
|
|
|
|
DownloadService service = CreateService(new FakeConfigService(config), dbService,
|
|
apiService, new DeterministicFileNameService(), authService);
|
|
ProgressRecorder progress = new();
|
|
PurchasedEntities.PaidPostCollection posts = CreatePaidPostCollectionForDrm(1,
|
|
$"https://cdn3.onlyfans.com/dash/files/{drmBaseFilename}.mpd,policy,signature,kvp,1,2");
|
|
|
|
DownloadResult result = await service.DownloadPaidPosts("creator", 1, folder, new Dictionary<string, long>(),
|
|
false, false, posts, progress);
|
|
|
|
Assert.Equal(1, result.TotalCount);
|
|
Assert.Equal(0, result.NewDownloads);
|
|
Assert.Equal(1, result.ExistingDownloads);
|
|
Assert.NotNull(dbService.LastUpdateMedia);
|
|
Assert.Equal($"{customName}.mp4", dbService.LastUpdateMedia.Value.filename);
|
|
Assert.Equal(1, progress.Total);
|
|
}
|
|
|
|
private static DownloadService CreateService(FakeConfigService configService, MediaTrackingDbService dbService,
|
|
StaticApiService? apiService = null, IFileNameService? fileNameService = null,
|
|
IAuthService? authService = null) =>
|
|
new(authService ?? new FakeAuthService(), configService, dbService,
|
|
fileNameService ?? new FakeFileNameService(),
|
|
apiService ?? new StaticApiService());
|
|
|
|
private static PostEntities.PostCollection CreatePostCollection(long mediaId, string serverFilename)
|
|
{
|
|
PostEntities.Medium media = new() { Id = mediaId };
|
|
PostEntities.ListItem post = new()
|
|
{
|
|
Id = 10,
|
|
PostedAt = new DateTime(2024, 1, 1),
|
|
Author = new OF_DL.Models.Entities.Common.Author { Id = 99 },
|
|
Media = [media]
|
|
};
|
|
|
|
return new PostEntities.PostCollection
|
|
{
|
|
Posts = new Dictionary<long, string> { { mediaId, $"https://example.com/{serverFilename}.jpg" } },
|
|
PostMedia = [media],
|
|
PostObjects = [post]
|
|
};
|
|
}
|
|
|
|
private static PurchasedEntities.PaidPostCollection CreatePaidPostCollectionForDrm(long mediaId, string drmUrl)
|
|
{
|
|
MessageEntities.Medium media = new() { Id = mediaId };
|
|
PurchasedEntities.ListItem post = new()
|
|
{
|
|
Id = 20,
|
|
PostedAt = new DateTime(2024, 1, 1),
|
|
Author = new OF_DL.Models.Entities.Common.Author { Id = 99 },
|
|
FromUser = null,
|
|
Media = [media]
|
|
};
|
|
|
|
return new PurchasedEntities.PaidPostCollection
|
|
{
|
|
PaidPosts = new Dictionary<long, string> { { mediaId, drmUrl } },
|
|
PaidPostMedia = [media],
|
|
PaidPostObjects = [post]
|
|
};
|
|
}
|
|
|
|
private static string NormalizeFolder(string folder) => folder.Replace("\\", "/");
|
|
|
|
private sealed class DeterministicFileNameService : IFileNameService
|
|
{
|
|
public Task<string> BuildFilename(string fileFormat, Dictionary<string, string> values) =>
|
|
Task.FromResult(fileFormat);
|
|
|
|
public Task<Dictionary<string, string>> GetFilename(object info, object media, object author,
|
|
List<string> selectedProperties, string username, Dictionary<string, long>? users = null) =>
|
|
Task.FromResult(new Dictionary<string, string>());
|
|
}
|
|
}
|