diff --git a/DiscordChatExporter.Cli.Tests/Fixtures/ExportWrapperFixture.cs b/DiscordChatExporter.Cli.Tests/Fixtures/ExportWrapperFixture.cs index 31ca318..7566989 100644 --- a/DiscordChatExporter.Cli.Tests/Fixtures/ExportWrapperFixture.cs +++ b/DiscordChatExporter.Cli.Tests/Fixtures/ExportWrapperFixture.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.Json; @@ -23,9 +24,12 @@ namespace DiscordChatExporter.Cli.Tests.Fixtures Guid.NewGuid().ToString() ); - public async ValueTask ExportAsHtmlAsync(Snowflake channelId) + public ExportWrapperFixture() => DirectoryEx.Reset(DirPath); + + private async ValueTask ExportAsync(Snowflake channelId, ExportFormat format) { - var filePath = Path.Combine(DirPath, channelId + ".html"); + var fileName = channelId.ToString() + '.' + format.GetFileExtension(); + var filePath = Path.Combine(DirPath, fileName); // Perform export only if it hasn't been done before if (!File.Exists(filePath)) @@ -35,83 +39,61 @@ namespace DiscordChatExporter.Cli.Tests.Fixtures TokenValue = Secrets.DiscordToken, IsBotToken = Secrets.IsDiscordTokenBot, ChannelIds = new[] { channelId }, - ExportFormat = ExportFormat.HtmlDark, + ExportFormat = format, OutputPath = filePath }.ExecuteAsync(new FakeConsole()); } - var data = await File.ReadAllTextAsync(filePath); + return await File.ReadAllTextAsync(filePath); + } + public async ValueTask ExportAsHtmlAsync(Snowflake channelId) + { + var data = await ExportAsync(channelId, ExportFormat.HtmlDark); return Html.Parse(data); } public async ValueTask ExportAsJsonAsync(Snowflake channelId) { - var filePath = Path.Combine(DirPath, channelId + ".json"); - - // Perform export only if it hasn't been done before - if (!File.Exists(filePath)) - { - await new ExportChannelsCommand - { - TokenValue = Secrets.DiscordToken, - IsBotToken = Secrets.IsDiscordTokenBot, - ChannelIds = new[] { channelId }, - ExportFormat = ExportFormat.Json, - OutputPath = filePath - }.ExecuteAsync(new FakeConsole()); - } - - var data = await File.ReadAllTextAsync(filePath); - + var data = await ExportAsync(channelId, ExportFormat.Json); return Json.Parse(data); } public async ValueTask ExportAsPlainTextAsync(Snowflake channelId) { - var filePath = Path.Combine(DirPath, channelId + ".txt"); - - // Perform export only if it hasn't been done before - if (!File.Exists(filePath)) - { - await new ExportChannelsCommand - { - TokenValue = Secrets.DiscordToken, - IsBotToken = Secrets.IsDiscordTokenBot, - ChannelIds = new[] { channelId }, - ExportFormat = ExportFormat.PlainText, - OutputPath = filePath - }.ExecuteAsync(new FakeConsole()); - } - - return await File.ReadAllTextAsync(filePath); + var data = await ExportAsync(channelId, ExportFormat.PlainText); + return data; } public async ValueTask ExportAsCsvAsync(Snowflake channelId) { - var filePath = Path.Combine(DirPath, channelId + ".csv"); + var data = await ExportAsync(channelId, ExportFormat.Csv); + return data; + } - // Perform export only if it hasn't been done before - if (!File.Exists(filePath)) - { - await new ExportChannelsCommand - { - TokenValue = Secrets.DiscordToken, - IsBotToken = Secrets.IsDiscordTokenBot, - ChannelIds = new[] { channelId }, - ExportFormat = ExportFormat.Csv, - OutputPath = filePath - }.ExecuteAsync(new FakeConsole()); - } + public async ValueTask> GetMessagesAsHtmlAsync(Snowflake channelId) + { + var document = await ExportAsHtmlAsync(channelId); + return document.QuerySelectorAll("[data-message-id]").ToArray(); + } - return await File.ReadAllTextAsync(filePath); + public async ValueTask> GetMessagesAsJsonAsync(Snowflake channelId) + { + var document = await ExportAsJsonAsync(channelId); + return document.GetProperty("messages").EnumerateArray().ToArray(); } public async ValueTask GetMessageAsHtmlAsync(Snowflake channelId, Snowflake messageId) { - var document = await ExportAsHtmlAsync(channelId); + var messages = await GetMessagesAsHtmlAsync(channelId); - var message = document.QuerySelector("#message-" + messageId); + var message = messages.SingleOrDefault(e => + string.Equals( + e.GetAttribute("data-message-id"), + messageId.ToString(), + StringComparison.OrdinalIgnoreCase + ) + ); if (message is null) { @@ -125,16 +107,15 @@ namespace DiscordChatExporter.Cli.Tests.Fixtures public async ValueTask GetMessageAsJsonAsync(Snowflake channelId, Snowflake messageId) { - var document = await ExportAsJsonAsync(channelId); + var messages = await GetMessagesAsJsonAsync(channelId); - var message = document - .GetProperty("messages") - .EnumerateArray() - .SingleOrDefault(j => string.Equals( + var message = messages.FirstOrDefault(j => + string.Equals( j.GetProperty("id").GetString(), messageId.ToString(), StringComparison.OrdinalIgnoreCase - )); + ) + ); if (message.ValueKind == JsonValueKind.Undefined) { @@ -146,15 +127,6 @@ namespace DiscordChatExporter.Cli.Tests.Fixtures return message; } - public void Dispose() - { - try - { - Directory.Delete(DirPath, true); - } - catch (DirectoryNotFoundException) - { - } - } + public void Dispose() => DirectoryEx.DeleteIfExists(DirPath); } } \ No newline at end of file diff --git a/DiscordChatExporter.Cli.Tests/Fixtures/TempOutputFixture.cs b/DiscordChatExporter.Cli.Tests/Fixtures/TempOutputFixture.cs new file mode 100644 index 0000000..75e6e16 --- /dev/null +++ b/DiscordChatExporter.Cli.Tests/Fixtures/TempOutputFixture.cs @@ -0,0 +1,23 @@ +using System; +using System.IO; +using DiscordChatExporter.Cli.Tests.Utils; + +namespace DiscordChatExporter.Cli.Tests.Fixtures +{ + public class TempOutputFixture : IDisposable + { + public string DirPath { get; } = Path.Combine( + Path.GetDirectoryName(typeof(TempOutputFixture).Assembly.Location) ?? Directory.GetCurrentDirectory(), + "Temp", + Guid.NewGuid().ToString() + ); + + public TempOutputFixture() => DirectoryEx.Reset(DirPath); + + public string GetTempFilePath(string fileName) => Path.Combine(DirPath, fileName); + + public string GetTempFilePath() => GetTempFilePath(Guid.NewGuid() + ".tmp"); + + public void Dispose() => DirectoryEx.DeleteIfExists(DirPath); + } +} \ No newline at end of file diff --git a/DiscordChatExporter.Cli.Tests/Specs/CsvWriting/GeneralSpecs.cs b/DiscordChatExporter.Cli.Tests/Specs/CsvWriting/ContentSpecs.cs similarity index 92% rename from DiscordChatExporter.Cli.Tests/Specs/CsvWriting/GeneralSpecs.cs rename to DiscordChatExporter.Cli.Tests/Specs/CsvWriting/ContentSpecs.cs index 2dfd9fb..d7e6b6f 100644 --- a/DiscordChatExporter.Cli.Tests/Specs/CsvWriting/GeneralSpecs.cs +++ b/DiscordChatExporter.Cli.Tests/Specs/CsvWriting/ContentSpecs.cs @@ -6,7 +6,7 @@ using Xunit; namespace DiscordChatExporter.Cli.Tests.Specs.CsvWriting { - public record GeneralSpecs(ExportWrapperFixture ExportWrapper) : IClassFixture + public record ContentSpecs(ExportWrapperFixture ExportWrapper) : IClassFixture { [Fact] public async Task Messages_are_exported_correctly() diff --git a/DiscordChatExporter.Cli.Tests/Specs/DateRangeSpecs.cs b/DiscordChatExporter.Cli.Tests/Specs/DateRangeSpecs.cs new file mode 100644 index 0000000..4545bb0 --- /dev/null +++ b/DiscordChatExporter.Cli.Tests/Specs/DateRangeSpecs.cs @@ -0,0 +1,160 @@ +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using CliFx.Infrastructure; +using DiscordChatExporter.Cli.Commands; +using DiscordChatExporter.Cli.Tests.Fixtures; +using DiscordChatExporter.Cli.Tests.Infra; +using DiscordChatExporter.Cli.Tests.TestData; +using DiscordChatExporter.Core.Discord; +using DiscordChatExporter.Core.Exporting; +using FluentAssertions; +using JsonExtensions; +using Xunit; + +namespace DiscordChatExporter.Cli.Tests.Specs +{ + public record DateRangeSpecs(TempOutputFixture TempOutput) : IClassFixture + { + [Fact] + public async Task Messages_filtered_after_specific_date_only_include_messages_sent_after_that_date() + { + // Arrange + var after = new DateTimeOffset(2021, 07, 24, 0, 0, 0, TimeSpan.Zero); + var filePath = TempOutput.GetTempFilePath(); + + // Act + await new ExportChannelsCommand + { + TokenValue = Secrets.DiscordToken, + IsBotToken = Secrets.IsDiscordTokenBot, + ChannelIds = new[] { ChannelIds.DateRangeTestCases }, + ExportFormat = ExportFormat.Json, + OutputPath = filePath, + After = Snowflake.FromDate(after) + }.ExecuteAsync(new FakeConsole()); + + var data = await File.ReadAllTextAsync(filePath); + var document = Json.Parse(data); + + var timestamps = document + .GetProperty("messages") + .EnumerateArray() + .Select(j => j.GetProperty("timestamp").GetDateTimeOffset()) + .ToArray(); + + // Assert + timestamps.All(t => t > after).Should().BeTrue(); + + timestamps.Should().BeEquivalentTo(new[] + { + new DateTimeOffset(2021, 07, 24, 13, 49, 13, TimeSpan.Zero), + new DateTimeOffset(2021, 07, 24, 14, 52, 38, TimeSpan.Zero), + new DateTimeOffset(2021, 07, 24, 14, 52, 39, TimeSpan.Zero), + new DateTimeOffset(2021, 07, 24, 14, 52, 40, TimeSpan.Zero), + new DateTimeOffset(2021, 09, 08, 14, 26, 35, TimeSpan.Zero) + }, o => + { + return o + .Using(ctx => + ctx.Subject.Should().BeCloseTo(ctx.Expectation, TimeSpan.FromSeconds(1)) + ) + .WhenTypeIs(); + }); + } + + [Fact] + public async Task Messages_filtered_before_specific_date_only_include_messages_sent_before_that_date() + { + // Arrange + var before = new DateTimeOffset(2021, 07, 24, 0, 0, 0, TimeSpan.Zero); + var filePath = TempOutput.GetTempFilePath(); + + // Act + await new ExportChannelsCommand + { + TokenValue = Secrets.DiscordToken, + IsBotToken = Secrets.IsDiscordTokenBot, + ChannelIds = new[] { ChannelIds.DateRangeTestCases }, + ExportFormat = ExportFormat.Json, + OutputPath = filePath, + Before = Snowflake.FromDate(before) + }.ExecuteAsync(new FakeConsole()); + + var data = await File.ReadAllTextAsync(filePath); + var document = Json.Parse(data); + + var timestamps = document + .GetProperty("messages") + .EnumerateArray() + .Select(j => j.GetProperty("timestamp").GetDateTimeOffset()) + .ToArray(); + + // Assert + timestamps.All(t => t < before).Should().BeTrue(); + + timestamps.Should().BeEquivalentTo(new[] + { + new DateTimeOffset(2021, 07, 19, 13, 34, 18, TimeSpan.Zero), + new DateTimeOffset(2021, 07, 19, 15, 58, 48, TimeSpan.Zero), + new DateTimeOffset(2021, 07, 19, 17, 23, 58, TimeSpan.Zero) + }, o => + { + return o + .Using(ctx => + ctx.Subject.Should().BeCloseTo(ctx.Expectation, TimeSpan.FromSeconds(1)) + ) + .WhenTypeIs(); + }); + } + + [Fact] + public async Task Messages_filtered_between_specific_dates_only_include_messages_sent_between_those_dates() + { + // Arrange + var after = new DateTimeOffset(2021, 07, 24, 0, 0, 0, TimeSpan.Zero); + var before = new DateTimeOffset(2021, 08, 01, 0, 0, 0, TimeSpan.Zero); + var filePath = TempOutput.GetTempFilePath(); + + // Act + await new ExportChannelsCommand + { + TokenValue = Secrets.DiscordToken, + IsBotToken = Secrets.IsDiscordTokenBot, + ChannelIds = new[] { ChannelIds.DateRangeTestCases }, + ExportFormat = ExportFormat.Json, + OutputPath = filePath, + Before = Snowflake.FromDate(before), + After = Snowflake.FromDate(after) + }.ExecuteAsync(new FakeConsole()); + + var data = await File.ReadAllTextAsync(filePath); + var document = Json.Parse(data); + + var timestamps = document + .GetProperty("messages") + .EnumerateArray() + .Select(j => j.GetProperty("timestamp").GetDateTimeOffset()) + .ToArray(); + + // Assert + timestamps.All(t => t < before && t > after).Should().BeTrue(); + + timestamps.Should().BeEquivalentTo(new[] + { + new DateTimeOffset(2021, 07, 24, 13, 49, 13, TimeSpan.Zero), + new DateTimeOffset(2021, 07, 24, 14, 52, 38, TimeSpan.Zero), + new DateTimeOffset(2021, 07, 24, 14, 52, 39, TimeSpan.Zero), + new DateTimeOffset(2021, 07, 24, 14, 52, 40, TimeSpan.Zero) + }, o => + { + return o + .Using(ctx => + ctx.Subject.Should().BeCloseTo(ctx.Expectation, TimeSpan.FromSeconds(1)) + ) + .WhenTypeIs(); + }); + } + } +} \ No newline at end of file diff --git a/DiscordChatExporter.Cli.Tests/Specs/FilterSpecs.cs b/DiscordChatExporter.Cli.Tests/Specs/FilterSpecs.cs new file mode 100644 index 0000000..a0aa6d6 --- /dev/null +++ b/DiscordChatExporter.Cli.Tests/Specs/FilterSpecs.cs @@ -0,0 +1,135 @@ +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using CliFx.Infrastructure; +using DiscordChatExporter.Cli.Commands; +using DiscordChatExporter.Cli.Tests.Fixtures; +using DiscordChatExporter.Cli.Tests.Infra; +using DiscordChatExporter.Cli.Tests.TestData; +using DiscordChatExporter.Core.Exporting; +using DiscordChatExporter.Core.Exporting.Filtering; +using FluentAssertions; +using JsonExtensions; +using Xunit; + +namespace DiscordChatExporter.Cli.Tests.Specs +{ + public record FilterSpecs(TempOutputFixture TempOutput) : IClassFixture + { + [Fact] + public async Task Messages_filtered_by_text_only_include_messages_that_contain_that_text() + { + // Arrange + var filePath = TempOutput.GetTempFilePath(); + + // Act + await new ExportChannelsCommand + { + TokenValue = Secrets.DiscordToken, + IsBotToken = Secrets.IsDiscordTokenBot, + ChannelIds = new[] { ChannelIds.FilterTestCases }, + ExportFormat = ExportFormat.Json, + OutputPath = filePath, + MessageFilter = MessageFilter.Parse("some text") + }.ExecuteAsync(new FakeConsole()); + + var data = await File.ReadAllTextAsync(filePath); + var document = Json.Parse(data); + + // Assert + document + .GetProperty("messages") + .EnumerateArray() + .Select(j => j.GetProperty("content").GetString()) + .Should() + .ContainSingle("Some random text"); + } + + [Fact] + public async Task Messages_filtered_by_author_only_include_messages_sent_by_that_author() + { + // Arrange + var filePath = TempOutput.GetTempFilePath(); + + // Act + await new ExportChannelsCommand + { + TokenValue = Secrets.DiscordToken, + IsBotToken = Secrets.IsDiscordTokenBot, + ChannelIds = new[] { ChannelIds.FilterTestCases }, + ExportFormat = ExportFormat.Json, + OutputPath = filePath, + MessageFilter = MessageFilter.Parse("from:Tyrrrz") + }.ExecuteAsync(new FakeConsole()); + + var data = await File.ReadAllTextAsync(filePath); + var document = Json.Parse(data); + + // Assert + document + .GetProperty("messages") + .EnumerateArray() + .Select(j => j.GetProperty("author").GetProperty("name").GetString()) + .Should() + .AllBe("Tyrrrz"); + } + + [Fact] + public async Task Messages_filtered_by_content_only_include_messages_that_have_that_content() + { + // Arrange + var filePath = TempOutput.GetTempFilePath(); + + // Act + await new ExportChannelsCommand + { + TokenValue = Secrets.DiscordToken, + IsBotToken = Secrets.IsDiscordTokenBot, + ChannelIds = new[] { ChannelIds.FilterTestCases }, + ExportFormat = ExportFormat.Json, + OutputPath = filePath, + MessageFilter = MessageFilter.Parse("has:image") + }.ExecuteAsync(new FakeConsole()); + + var data = await File.ReadAllTextAsync(filePath); + var document = Json.Parse(data); + + // Assert + document + .GetProperty("messages") + .EnumerateArray() + .Select(j => j.GetProperty("content").GetString()) + .Should() + .ContainSingle("This has image"); + } + + [Fact] + public async Task Messages_filtered_by_mention_only_include_messages_that_have_that_mention() + { + // Arrange + var filePath = TempOutput.GetTempFilePath(); + + // Act + await new ExportChannelsCommand + { + TokenValue = Secrets.DiscordToken, + IsBotToken = Secrets.IsDiscordTokenBot, + ChannelIds = new[] { ChannelIds.FilterTestCases }, + ExportFormat = ExportFormat.Json, + OutputPath = filePath, + MessageFilter = MessageFilter.Parse("mentions:Tyrrrz") + }.ExecuteAsync(new FakeConsole()); + + var data = await File.ReadAllTextAsync(filePath); + var document = Json.Parse(data); + + // Assert + document + .GetProperty("messages") + .EnumerateArray() + .Select(j => j.GetProperty("content").GetString()) + .Should() + .ContainSingle("This has mention"); + } + } +} \ No newline at end of file diff --git a/DiscordChatExporter.Cli.Tests/Specs/HtmlWriting/GeneralSpecs.cs b/DiscordChatExporter.Cli.Tests/Specs/HtmlWriting/ContentSpecs.cs similarity index 62% rename from DiscordChatExporter.Cli.Tests/Specs/HtmlWriting/GeneralSpecs.cs rename to DiscordChatExporter.Cli.Tests/Specs/HtmlWriting/ContentSpecs.cs index 3b817d9..829cc58 100644 --- a/DiscordChatExporter.Cli.Tests/Specs/HtmlWriting/GeneralSpecs.cs +++ b/DiscordChatExporter.Cli.Tests/Specs/HtmlWriting/ContentSpecs.cs @@ -8,26 +8,16 @@ using Xunit; namespace DiscordChatExporter.Cli.Tests.Specs.HtmlWriting { - public record GeneralSpecs(ExportWrapperFixture ExportWrapper) : IClassFixture + public record ContentSpecs(ExportWrapperFixture ExportWrapper) : IClassFixture { [Fact] public async Task Messages_are_exported_correctly() { // Act - var document = await ExportWrapper.ExportAsHtmlAsync(ChannelIds.DateRangeTestCases); - - var messageIds = document - .QuerySelectorAll(".chatlog__message") - .Select(e => e.GetAttribute("data-message-id")) - .ToArray(); - - var messageTexts = document - .QuerySelectorAll(".chatlog__content") - .Select(e => e.Text().Trim()) - .ToArray(); + var messages = await ExportWrapper.GetMessagesAsHtmlAsync(ChannelIds.DateRangeTestCases); // Assert - messageIds.Should().Equal( + messages.Select(e => e.GetAttribute("data-message-id")).Should().Equal( "866674314627121232", "866710679758045195", "866732113319428096", @@ -38,7 +28,7 @@ namespace DiscordChatExporter.Cli.Tests.Specs.HtmlWriting "885169254029213696" ); - messageTexts.Should().Equal( + messages.Select(e => e.QuerySelector(".chatlog__content")?.Text().Trim()).Should().Equal( "Hello world", "Goodbye world", "Foo bar", diff --git a/DiscordChatExporter.Cli.Tests/Specs/JsonWriting/GeneralSpecs.cs b/DiscordChatExporter.Cli.Tests/Specs/JsonWriting/ContentSpecs.cs similarity index 59% rename from DiscordChatExporter.Cli.Tests/Specs/JsonWriting/GeneralSpecs.cs rename to DiscordChatExporter.Cli.Tests/Specs/JsonWriting/ContentSpecs.cs index 75b827a..7b98023 100644 --- a/DiscordChatExporter.Cli.Tests/Specs/JsonWriting/GeneralSpecs.cs +++ b/DiscordChatExporter.Cli.Tests/Specs/JsonWriting/ContentSpecs.cs @@ -7,28 +7,16 @@ using Xunit; namespace DiscordChatExporter.Cli.Tests.Specs.JsonWriting { - public record GeneralSpecs(ExportWrapperFixture ExportWrapper) : IClassFixture + public record ContentSpecs(ExportWrapperFixture ExportWrapper) : IClassFixture { [Fact] public async Task Messages_are_exported_correctly() { // Act - var document = await ExportWrapper.ExportAsJsonAsync(ChannelIds.DateRangeTestCases); - - var messageIds = document - .GetProperty("messages") - .EnumerateArray() - .Select(j => j.GetProperty("id").GetString()) - .ToArray(); - - var messageTexts = document - .GetProperty("messages") - .EnumerateArray() - .Select(j => j.GetProperty("content").GetString()) - .ToArray(); + var messages = await ExportWrapper.GetMessagesAsJsonAsync(ChannelIds.DateRangeTestCases); // Assert - messageIds.Should().Equal( + messages.Select(j => j.GetProperty("id").GetString()).Should().Equal( "866674314627121232", "866710679758045195", "866732113319428096", @@ -39,7 +27,7 @@ namespace DiscordChatExporter.Cli.Tests.Specs.JsonWriting "885169254029213696" ); - messageTexts.Should().Equal( + messages.Select(j => j.GetProperty("content").GetString()).Should().Equal( "Hello world", "Goodbye world", "Foo bar", diff --git a/DiscordChatExporter.Cli.Tests/Specs/PartitioningSpecs.cs b/DiscordChatExporter.Cli.Tests/Specs/PartitioningSpecs.cs new file mode 100644 index 0000000..4fc363a --- /dev/null +++ b/DiscordChatExporter.Cli.Tests/Specs/PartitioningSpecs.cs @@ -0,0 +1,67 @@ +using System.IO; +using System.Threading.Tasks; +using CliFx.Infrastructure; +using DiscordChatExporter.Cli.Commands; +using DiscordChatExporter.Cli.Tests.Fixtures; +using DiscordChatExporter.Cli.Tests.Infra; +using DiscordChatExporter.Cli.Tests.TestData; +using DiscordChatExporter.Core.Exporting; +using DiscordChatExporter.Core.Exporting.Partitioning; +using FluentAssertions; +using Xunit; + +namespace DiscordChatExporter.Cli.Tests.Specs +{ + public record PartitioningSpecs(TempOutputFixture TempOutput) : IClassFixture + { + [Fact] + public async Task Messages_partitioned_by_count_are_split_into_multiple_files_correctly() + { + // Arrange + var filePath = TempOutput.GetTempFilePath(); + var fileNameWithoutExt = Path.GetFileNameWithoutExtension(filePath); + var dirPath = Path.GetDirectoryName(filePath) ?? Directory.GetCurrentDirectory(); + + // Act + await new ExportChannelsCommand + { + TokenValue = Secrets.DiscordToken, + IsBotToken = Secrets.IsDiscordTokenBot, + ChannelIds = new[] { ChannelIds.DateRangeTestCases }, + ExportFormat = ExportFormat.HtmlDark, + OutputPath = filePath, + PartitionLimit = PartitionLimit.Parse("3") + }.ExecuteAsync(new FakeConsole()); + + // Assert + Directory.EnumerateFiles(dirPath, fileNameWithoutExt + "*") + .Should() + .HaveCount(3); + } + + [Fact] + public async Task Messages_partitioned_by_file_size_are_split_into_multiple_files_correctly() + { + // Arrange + var filePath = TempOutput.GetTempFilePath(); + var fileNameWithoutExt = Path.GetFileNameWithoutExtension(filePath); + var dirPath = Path.GetDirectoryName(filePath) ?? Directory.GetCurrentDirectory(); + + // Act + await new ExportChannelsCommand + { + TokenValue = Secrets.DiscordToken, + IsBotToken = Secrets.IsDiscordTokenBot, + ChannelIds = new[] { ChannelIds.DateRangeTestCases }, + ExportFormat = ExportFormat.HtmlDark, + OutputPath = filePath, + PartitionLimit = PartitionLimit.Parse("20kb") + }.ExecuteAsync(new FakeConsole()); + + // Assert + Directory.EnumerateFiles(dirPath, fileNameWithoutExt + "*") + .Should() + .HaveCount(2); + } + } +} \ No newline at end of file diff --git a/DiscordChatExporter.Cli.Tests/Specs/PlainTextWriting/GeneralSpecs.cs b/DiscordChatExporter.Cli.Tests/Specs/PlainTextWriting/ContentSpecs.cs similarity index 92% rename from DiscordChatExporter.Cli.Tests/Specs/PlainTextWriting/GeneralSpecs.cs rename to DiscordChatExporter.Cli.Tests/Specs/PlainTextWriting/ContentSpecs.cs index 4588458..3c18e6a 100644 --- a/DiscordChatExporter.Cli.Tests/Specs/PlainTextWriting/GeneralSpecs.cs +++ b/DiscordChatExporter.Cli.Tests/Specs/PlainTextWriting/ContentSpecs.cs @@ -6,7 +6,7 @@ using Xunit; namespace DiscordChatExporter.Cli.Tests.Specs.PlainTextWriting { - public record GeneralSpecs(ExportWrapperFixture ExportWrapper) : IClassFixture + public record ContentSpecs(ExportWrapperFixture ExportWrapper) : IClassFixture { [Fact] public async Task Messages_are_exported_correctly() diff --git a/DiscordChatExporter.Cli.Tests/Specs/SelfContainedSpecs.cs b/DiscordChatExporter.Cli.Tests/Specs/SelfContainedSpecs.cs new file mode 100644 index 0000000..568d6b6 --- /dev/null +++ b/DiscordChatExporter.Cli.Tests/Specs/SelfContainedSpecs.cs @@ -0,0 +1,49 @@ +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using CliFx.Infrastructure; +using DiscordChatExporter.Cli.Commands; +using DiscordChatExporter.Cli.Tests.Fixtures; +using DiscordChatExporter.Cli.Tests.Infra; +using DiscordChatExporter.Cli.Tests.TestData; +using DiscordChatExporter.Cli.Tests.Utils; +using DiscordChatExporter.Core.Exporting; +using FluentAssertions; +using Xunit; + +namespace DiscordChatExporter.Cli.Tests.Specs +{ + public record SelfContainedSpecs(TempOutputFixture TempOutput) : IClassFixture + { + [Fact] + public async Task Messages_in_self_contained_export_only_reference_local_file_resources() + { + // Arrange + var filePath = TempOutput.GetTempFilePath(); + var dirPath = Path.GetDirectoryName(filePath) ?? Directory.GetCurrentDirectory(); + + // Act + await new ExportChannelsCommand + { + TokenValue = Secrets.DiscordToken, + IsBotToken = Secrets.IsDiscordTokenBot, + ChannelIds = new[] { ChannelIds.SelfContainedTestCases }, + ExportFormat = ExportFormat.HtmlDark, + OutputPath = filePath, + ShouldDownloadMedia = true + }.ExecuteAsync(new FakeConsole()); + + var data = await File.ReadAllTextAsync(filePath); + var document = Html.Parse(data); + + // Assert + document + .QuerySelectorAll("body [src]") + .Select(e => e.GetAttribute("src")!) + .Select(f => Path.GetFullPath(f, dirPath)) + .All(File.Exists) + .Should() + .BeTrue(); + } + } +} \ No newline at end of file diff --git a/DiscordChatExporter.Cli.Tests/TestData/ChannelIds.cs b/DiscordChatExporter.Cli.Tests/TestData/ChannelIds.cs index 4bc6be0..f071c1c 100644 --- a/DiscordChatExporter.Cli.Tests/TestData/ChannelIds.cs +++ b/DiscordChatExporter.Cli.Tests/TestData/ChannelIds.cs @@ -10,8 +10,12 @@ namespace DiscordChatExporter.Cli.Tests.TestData public static Snowflake EmbedTestCases { get; } = Snowflake.Parse("866472452459462687"); + public static Snowflake FilterTestCases { get; } = Snowflake.Parse("866744075033641020"); + public static Snowflake MentionTestCases { get; } = Snowflake.Parse("866458801389174794"); public static Snowflake ReplyTestCases { get; } = Snowflake.Parse("866459871934677052"); + + public static Snowflake SelfContainedTestCases { get; } = Snowflake.Parse("887441432678379560"); } } \ No newline at end of file diff --git a/DiscordChatExporter.Cli.Tests/Utils/DirectoryEx.cs b/DiscordChatExporter.Cli.Tests/Utils/DirectoryEx.cs new file mode 100644 index 0000000..f525143 --- /dev/null +++ b/DiscordChatExporter.Cli.Tests/Utils/DirectoryEx.cs @@ -0,0 +1,24 @@ +using System.IO; + +namespace DiscordChatExporter.Cli.Tests.Utils +{ + internal static class DirectoryEx + { + public static void DeleteIfExists(string dirPath, bool recursive = true) + { + try + { + Directory.Delete(dirPath, recursive); + } + catch (DirectoryNotFoundException) + { + } + } + + public static void Reset(string dirPath) + { + DeleteIfExists(dirPath); + Directory.CreateDirectory(dirPath); + } + } +} \ No newline at end of file