303 lines
13 KiB
C#
303 lines
13 KiB
C#
using Newtonsoft.Json.Linq;
|
|
using OF_DL.Models.Config;
|
|
using OF_DL.Models.Downloads;
|
|
using OF_DL.Services;
|
|
using MessageEntities = OF_DL.Models.Entities.Messages;
|
|
using PostEntities = OF_DL.Models.Entities.Posts;
|
|
using PurchasedEntities = OF_DL.Models.Entities.Purchased;
|
|
|
|
namespace OF_DL.Tests.Services;
|
|
|
|
public class DownloadOrchestrationServiceTests
|
|
{
|
|
[Fact]
|
|
public async Task GetAvailableUsersAsync_FiltersIgnoredUsers()
|
|
{
|
|
Config config = CreateConfig(c =>
|
|
{
|
|
c.IncludeExpiredSubscriptions = true;
|
|
c.IgnoredUsersListName = "ignored";
|
|
});
|
|
FakeConfigService configService = new(config);
|
|
ConfigurableApiService apiService = new()
|
|
{
|
|
ActiveSubscriptionsHandler = (_, _) =>
|
|
Task.FromResult<Dictionary<string, long>?>(new Dictionary<string, long> { { "alice", 1 } }),
|
|
ExpiredSubscriptionsHandler = (_, _) =>
|
|
Task.FromResult<Dictionary<string, long>?>(new Dictionary<string, long> { { "bob", 2 } }),
|
|
ListsHandler =
|
|
_ => Task.FromResult<Dictionary<string, long>?>(new Dictionary<string, long> { { "ignored", 10 } }),
|
|
ListUsersHandler = _ => Task.FromResult<List<string>?>(["alice"])
|
|
};
|
|
UserTrackingDbService dbService = new();
|
|
DownloadOrchestrationService service =
|
|
new(apiService, configService, new OrchestrationDownloadServiceStub(), dbService);
|
|
|
|
UserListResult result = await service.GetAvailableUsersAsync();
|
|
|
|
Assert.Single(result.Users);
|
|
Assert.True(result.Users.ContainsKey("bob"));
|
|
Assert.Null(result.IgnoredListError);
|
|
Assert.NotNull(dbService.CreatedUsers);
|
|
Assert.True(dbService.CreatedUsers.ContainsKey("bob"));
|
|
Assert.False(dbService.CreatedUsers.ContainsKey("alice"));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetAvailableUsersAsync_SetsIgnoredListErrorWhenMissing()
|
|
{
|
|
Config config = CreateConfig(c =>
|
|
{
|
|
c.IncludeExpiredSubscriptions = false;
|
|
c.IgnoredUsersListName = "missing";
|
|
});
|
|
FakeConfigService configService = new(config);
|
|
ConfigurableApiService apiService = new()
|
|
{
|
|
ActiveSubscriptionsHandler = (_, _) =>
|
|
Task.FromResult<Dictionary<string, long>?>(new Dictionary<string, long> { { "alice", 1 } }),
|
|
ListsHandler = _ => Task.FromResult<Dictionary<string, long>?>(new Dictionary<string, long>())
|
|
};
|
|
UserTrackingDbService dbService = new();
|
|
DownloadOrchestrationService service =
|
|
new(apiService, configService, new OrchestrationDownloadServiceStub(), dbService);
|
|
|
|
UserListResult result = await service.GetAvailableUsersAsync();
|
|
|
|
Assert.NotNull(result.IgnoredListError);
|
|
Assert.Single(result.Users);
|
|
Assert.True(result.Users.ContainsKey("alice"));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetUsersForListAsync_ReturnsUsersInList()
|
|
{
|
|
FakeConfigService configService = new(CreateConfig());
|
|
ConfigurableApiService apiService = new() { ListUsersHandler = _ => Task.FromResult<List<string>?>(["bob"]) };
|
|
DownloadOrchestrationService service =
|
|
new(apiService, configService, new OrchestrationDownloadServiceStub(), new UserTrackingDbService());
|
|
Dictionary<string, long> allUsers = new() { { "alice", 1 }, { "bob", 2 } };
|
|
Dictionary<string, long> lists = new() { { "mylist", 5 } };
|
|
|
|
Dictionary<string, long> result = await service.GetUsersForListAsync("mylist", allUsers, lists);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(2, result["bob"]);
|
|
}
|
|
|
|
[Fact]
|
|
public void ResolveDownloadPath_UsesConfiguredPathWhenSet()
|
|
{
|
|
Config config = CreateConfig(c => c.DownloadPath = "C:\\Downloads");
|
|
DownloadOrchestrationService service =
|
|
new(new ConfigurableApiService(), new FakeConfigService(config), new OrchestrationDownloadServiceStub(),
|
|
new UserTrackingDbService());
|
|
|
|
string path = service.ResolveDownloadPath("creator");
|
|
|
|
Assert.Equal(Path.Combine("C:\\Downloads", "creator"), path);
|
|
}
|
|
|
|
[Fact]
|
|
public void ResolveDownloadPath_UsesDefaultWhenBlank()
|
|
{
|
|
Config config = CreateConfig(c => c.DownloadPath = "");
|
|
DownloadOrchestrationService service =
|
|
new(new ConfigurableApiService(), new FakeConfigService(config), new OrchestrationDownloadServiceStub(),
|
|
new UserTrackingDbService());
|
|
|
|
string path = service.ResolveDownloadPath("creator");
|
|
|
|
Assert.Equal("__user_data__/sites/OnlyFans/creator", path);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PrepareUserFolderAsync_CreatesFolderAndDb()
|
|
{
|
|
using TempFolder temp = new();
|
|
string userPath = Path.Combine(temp.Path, "creator");
|
|
UserTrackingDbService dbService = new();
|
|
DownloadOrchestrationService service =
|
|
new(new ConfigurableApiService(), new FakeConfigService(CreateConfig()),
|
|
new OrchestrationDownloadServiceStub(), dbService);
|
|
|
|
await service.PrepareUserFolderAsync("creator", 99, userPath);
|
|
|
|
Assert.True(Directory.Exists(userPath));
|
|
Assert.True(dbService.CheckedUser.HasValue);
|
|
Assert.Equal("creator", dbService.CheckedUser.Value.user.Key);
|
|
Assert.Equal(99, dbService.CheckedUser.Value.user.Value);
|
|
Assert.Equal(userPath, dbService.CheckedUser.Value.path);
|
|
Assert.Contains(userPath, dbService.CreatedDbs);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DownloadSinglePostAsync_WhenMissingPost_SendsMessage()
|
|
{
|
|
ConfigurableApiService apiService = new()
|
|
{
|
|
PostHandler = (_, _) => Task.FromResult(new PostEntities.SinglePostCollection())
|
|
};
|
|
OrchestrationDownloadServiceStub downloadService = new();
|
|
RecordingDownloadEventHandler eventHandler = new();
|
|
DownloadOrchestrationService service =
|
|
new(apiService, new FakeConfigService(CreateConfig()), downloadService, new UserTrackingDbService());
|
|
|
|
await service.DownloadSinglePostAsync("creator", 42, "/tmp", new Dictionary<string, long>(),
|
|
true, true, eventHandler);
|
|
|
|
Assert.Contains("Getting Post", eventHandler.Messages);
|
|
Assert.Contains("Couldn't find post", eventHandler.Messages);
|
|
Assert.False(downloadService.SinglePostCalled);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DownloadSinglePostAsync_WhenDownloaded_SendsDownloadedMessage()
|
|
{
|
|
PostEntities.SinglePostCollection collection = new()
|
|
{
|
|
SinglePosts = new Dictionary<long, string> { { 1, "https://example.com/post.jpg" } }
|
|
};
|
|
ConfigurableApiService apiService = new() { PostHandler = (_, _) => Task.FromResult(collection) };
|
|
OrchestrationDownloadServiceStub downloadService = new()
|
|
{
|
|
SinglePostResult = new DownloadResult { NewDownloads = 1, TotalCount = 1 }
|
|
};
|
|
RecordingDownloadEventHandler eventHandler = new();
|
|
DownloadOrchestrationService service =
|
|
new(apiService, new FakeConfigService(CreateConfig()), downloadService, new UserTrackingDbService());
|
|
|
|
await service.DownloadSinglePostAsync("creator", 99, "/tmp", new Dictionary<string, long>(),
|
|
true, true, eventHandler);
|
|
|
|
Assert.Contains("Post 99 downloaded", eventHandler.Messages);
|
|
Assert.True(downloadService.SinglePostCalled);
|
|
Assert.True(eventHandler.ProgressCalls.Count > 0);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DownloadSinglePaidMessageAsync_WithPreviewDownloads()
|
|
{
|
|
PurchasedEntities.SinglePaidMessageCollection collection = new()
|
|
{
|
|
PreviewSingleMessages = new Dictionary<long, string> { { 1, "https://example.com/preview.jpg" } },
|
|
SingleMessages = new Dictionary<long, string> { { 2, "https://example.com/full.jpg" } },
|
|
SingleMessageObjects = [new MessageEntities.SingleMessage()]
|
|
};
|
|
ConfigurableApiService apiService = new() { PaidMessageHandler = (_, _) => Task.FromResult(collection) };
|
|
OrchestrationDownloadServiceStub downloadService = new()
|
|
{
|
|
SinglePaidMessageResult = new DownloadResult { TotalCount = 1, NewDownloads = 1 }
|
|
};
|
|
RecordingDownloadEventHandler eventHandler = new();
|
|
DownloadOrchestrationService service =
|
|
new(apiService, new FakeConfigService(CreateConfig()), downloadService, new UserTrackingDbService());
|
|
|
|
await service.DownloadSinglePaidMessageAsync("creator", 5, "/tmp", new Dictionary<string, long>(),
|
|
true, true, eventHandler);
|
|
|
|
Assert.Contains(eventHandler.ContentFound, entry => entry.contentType == "Paid Messages");
|
|
Assert.True(downloadService.SinglePaidMessageCalled);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DownloadCreatorContentAsync_DownloadsStoriesWhenEnabled()
|
|
{
|
|
using TempFolder temp = new();
|
|
string path = Path.Combine(temp.Path, "creator");
|
|
Config config = CreateConfig(c =>
|
|
{
|
|
c.DownloadStories = true;
|
|
c.ShowScrapeSize = false;
|
|
});
|
|
FakeConfigService configService = new(config);
|
|
ConfigurableApiService apiService = new()
|
|
{
|
|
MediaHandler = (_, _, _, _) => Task.FromResult<Dictionary<long, string>?>(
|
|
new Dictionary<long, string>
|
|
{
|
|
{ 1, "https://example.com/one.jpg" }, { 2, "https://example.com/two.jpg" }
|
|
})
|
|
};
|
|
OrchestrationDownloadServiceStub downloadService = new()
|
|
{
|
|
StoriesResult = new DownloadResult { TotalCount = 2, NewDownloads = 2 }
|
|
};
|
|
RecordingDownloadEventHandler eventHandler = new();
|
|
DownloadOrchestrationService service =
|
|
new(apiService, configService, downloadService, new UserTrackingDbService());
|
|
|
|
CreatorDownloadResult result = await service.DownloadCreatorContentAsync("creator", 1, path,
|
|
new Dictionary<string, long>(), true, true, eventHandler);
|
|
|
|
Assert.Equal(2, result.StoriesCount);
|
|
Assert.Contains(eventHandler.ContentFound, entry => entry.contentType == "Stories");
|
|
Assert.Contains(eventHandler.DownloadCompletes, entry => entry.contentType == "Stories");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ResolveUsernameAsync_ReturnsDeletedPlaceholderWhenMissing()
|
|
{
|
|
ConfigurableApiService apiService = new() { UserInfoByIdHandler = _ => Task.FromResult<JObject?>(null) };
|
|
DownloadOrchestrationService service =
|
|
new(apiService, new FakeConfigService(CreateConfig()), new OrchestrationDownloadServiceStub(),
|
|
new UserTrackingDbService());
|
|
|
|
string? result = await service.ResolveUsernameAsync(123);
|
|
|
|
Assert.Equal("Deleted User - 123", result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ResolveUsernameAsync_ReturnsUsernameWhenPresent()
|
|
{
|
|
JObject payload = new() { ["5"] = new JObject { ["username"] = "creator" } };
|
|
ConfigurableApiService apiService = new() { UserInfoByIdHandler = _ => Task.FromResult<JObject?>(payload) };
|
|
DownloadOrchestrationService service =
|
|
new(apiService, new FakeConfigService(CreateConfig()), new OrchestrationDownloadServiceStub(),
|
|
new UserTrackingDbService());
|
|
|
|
string? result = await service.ResolveUsernameAsync(5);
|
|
|
|
Assert.Equal("creator", result);
|
|
}
|
|
|
|
private static Config CreateConfig(Action<Config>? configure = null)
|
|
{
|
|
Config config = new()
|
|
{
|
|
DownloadAvatarHeaderPhoto = false,
|
|
DownloadPaidPosts = false,
|
|
DownloadPosts = false,
|
|
DownloadArchived = false,
|
|
DownloadStreams = false,
|
|
DownloadStories = false,
|
|
DownloadHighlights = false,
|
|
DownloadMessages = false,
|
|
DownloadPaidMessages = false,
|
|
DownloadImages = false,
|
|
DownloadVideos = false,
|
|
DownloadAudios = false,
|
|
IncludeExpiredSubscriptions = false,
|
|
IncludeRestrictedSubscriptions = false,
|
|
SkipAds = false,
|
|
IgnoreOwnMessages = false,
|
|
DownloadPostsIncrementally = false,
|
|
BypassContentForCreatorsWhoNoLongerExist = false,
|
|
DownloadDuplicatedMedia = false,
|
|
DownloadOnlySpecificDates = false,
|
|
NonInteractiveModePurchasedTab = false,
|
|
LimitDownloadRate = false,
|
|
FolderPerPaidPost = false,
|
|
FolderPerPost = false,
|
|
FolderPerPaidMessage = false,
|
|
FolderPerMessage = false,
|
|
ShowScrapeSize = false,
|
|
DisableBrowserAuth = false
|
|
};
|
|
|
|
configure?.Invoke(config);
|
|
return config;
|
|
}
|
|
}
|