diff --git a/Source/VersOne.Epub.Test/Unit/Readers/PackageReaderTests.cs b/Source/VersOne.Epub.Test/Unit/Readers/PackageReaderTests.cs index 7961101..04e58b2 100644 --- a/Source/VersOne.Epub.Test/Unit/Readers/PackageReaderTests.cs +++ b/Source/VersOne.Epub.Test/Unit/Readers/PackageReaderTests.cs @@ -110,7 +110,7 @@ public class PackageReaderTests - + @@ -122,6 +122,202 @@ public class PackageReaderTests """; + private const string OPF_FILE_WITH_NON_SUPPORTED_EPUB_VERSION = $""" + + + + + + + """; + + private const string OPF_FILE_WITHOUT_PACKAGE = $""" + + + + + + + """; + + private const string OPF_FILE_WITHOUT_METADATA = $""" + + + + + + """; + + private const string OPF_FILE_WITHOUT_MANIFEST = $""" + + + + + + """; + + private const string OPF_FILE_WITHOUT_SPINE = $""" + + + + + + """; + + private const string OPF_FILE_WITHOUT_ID_IN_MANIFEST_ITEM = $""" + + + + + + + + + """; + + private const string OPF_FILE_WITH_EMPTY_ID_IN_MANIFEST_ITEM = $""" + + + + + + + + + """; + + private const string OPF_FILE_WITHOUT_HREF_IN_MANIFEST_ITEM = $""" + + + + + + + + + """; + + private const string OPF_FILE_WITH_EMPTY_HREF_IN_MANIFEST_ITEM = $""" + + + + + + + + + """; + + private const string OPF_FILE_WITHOUT_MEDIA_TYPE_IN_MANIFEST_ITEM = $""" + + + + + + + + + """; + + private const string OPF_FILE_WITH_EMPTY_MEDIA_TYPE_IN_MANIFEST_ITEM = $""" + + + + + + + + + """; + + private const string EPUB2_OPF_FILE_WITHOUT_SPINE_TOC = $""" + + + + + + + """; + + private const string EPUB2_OPF_FILE_WITH_EMPTY_SPINE_TOC = $""" + + + + + + + """; + + private const string OPF_FILE_WITHOUT_IDREF_IN_SPINE_ITEMREF = $""" + + + + + + + + + """; + + private const string OPF_FILE_WITH_EMPTY_IDREF_IN_SPINE_ITEMREF = $""" + + + + + + + + + """; + + private const string OPF_FILE_WITHOUT_TYPE_IN_GUIDE_REFERENCE = $""" + + + + + + + + + + """; + + private const string OPF_FILE_WITH_EMPTY_TYPE_IN_GUIDE_REFERENCE = $""" + + + + + + + + + + """; + + private const string OPF_FILE_WITHOUT_HREF_IN_GUIDE_REFERENCE = $""" + + + + + + + + + + """; + + private const string OPF_FILE_WITH_EMPTY_HREF_IN_GUIDE_REFERENCE = $""" + + + + + + + + + + """; + private static EpubMetadata EmptyMetadata => new() { @@ -551,6 +747,16 @@ private static EpubPackage FullPackage } } + private static EpubPackage Epub2PackageWithoutSpineToc => + new() + { + EpubVersion = EpubVersion.EPUB_2, + Metadata = EmptyMetadata, + Manifest = new EpubManifest(), + Spine = new EpubSpine() + }; + + public static IEnumerable ReadMinimalPackageAsyncTestData { get @@ -573,24 +779,202 @@ public static IEnumerable ReadFullPackageAsyncTestData [MemberData(nameof(ReadMinimalPackageAsyncTestData))] public async void ReadMinimalPackageAsyncTest(string opfFileContent, EpubPackage expectedEpubPackage) { - TestZipFile testZipFile = new(); - testZipFile.AddEntry(CONTAINER_FILE_PATH, CONTAINER_FILE); - testZipFile.AddEntry(OPF_FILE_PATH, opfFileContent); - EpubPackage actualEpubPackage = await PackageReader.ReadPackageAsync(testZipFile, OPF_FILE_PATH, new EpubReaderOptions()); - CompareEpubPackages(expectedEpubPackage, actualEpubPackage); + await TestSuccessfulReadOperation(opfFileContent, expectedEpubPackage); } [Theory(DisplayName = "Reading a full OPF package should succeed")] [MemberData(nameof(ReadFullPackageAsyncTestData))] public async void ReadFullPackageAsyncTest(string opfFileContent, EpubPackage expectedEpubPackage) + { + await TestSuccessfulReadOperation(opfFileContent, expectedEpubPackage); + } + + [Fact(DisplayName = "Trying to read OPF package from the EPUB file with no OPF package should fail with EpubContainerException")] + public async void ReadPackageWithNoOpfFileTest() { TestZipFile testZipFile = new(); testZipFile.AddEntry(CONTAINER_FILE_PATH, CONTAINER_FILE); - testZipFile.AddEntry(OPF_FILE_PATH, opfFileContent); - EpubPackage actualEpubPackage = await PackageReader.ReadPackageAsync(testZipFile, OPF_FILE_PATH, new EpubReaderOptions()); + await Assert.ThrowsAsync(() => PackageReader.ReadPackageAsync(testZipFile, OPF_FILE_PATH, new EpubReaderOptions())); + } + + [Fact(DisplayName = "Trying to read OPF package with non-supported EPUB version should fail with EpubPackageException")] + public async void ReadPackageWithNonSupportedEpubVersionTest() + { + await TestFailingReadOperation(OPF_FILE_WITH_NON_SUPPORTED_EPUB_VERSION); + } + + [Fact(DisplayName = "Trying to read OPF package without package XML node should fail with EpubPackageException")] + public async void ReadPackageWithoutPackageNodeTest() + { + await TestFailingReadOperation(OPF_FILE_WITHOUT_PACKAGE); + } + + [Fact(DisplayName = "Trying to read OPF package without metadata XML node should fail with EpubPackageException")] + public async void ReadPackageWithoutMetadataNodeTest() + { + await TestFailingReadOperation(OPF_FILE_WITHOUT_METADATA); + } + + [Fact(DisplayName = "Trying to read OPF package without manifest XML node should fail with EpubPackageException")] + public async void ReadPackageWithoutManifestNodeTest() + { + await TestFailingReadOperation(OPF_FILE_WITHOUT_MANIFEST); + } + + [Fact(DisplayName = "Trying to read OPF package without spine XML node should fail with EpubPackageException")] + public async void ReadPackageWithoutSpineNodeTest() + { + await TestFailingReadOperation(OPF_FILE_WITHOUT_SPINE); + } + + [Fact(DisplayName = "Trying to read OPF package without ID attribute in a manifest item XML node should fail with EpubPackageException")] + public async void ReadPackageWithoutManifestItemIdTest() + { + await TestFailingReadOperation(OPF_FILE_WITHOUT_ID_IN_MANIFEST_ITEM); + } + + [Fact(DisplayName = "Trying to read OPF package with empty ID attribute in a manifest item XML node should fail with EpubPackageException")] + public async void ReadPackageWithEmptyManifestItemIdTest() + { + await TestFailingReadOperation(OPF_FILE_WITH_EMPTY_ID_IN_MANIFEST_ITEM); + } + + [Fact(DisplayName = "Trying to read OPF package without ID attribute in a manifest item XML node with SkipInvalidManifestItems = true should succeed")] + public async void ReadPackageWithoutManifestItemIdWithSkippingInvalidManifestItemsTest() + { + await TestSuccessfulReadOperationWithSkippingInvalidManifestItems(OPF_FILE_WITHOUT_ID_IN_MANIFEST_ITEM, MinimalEpub3Package); + } + + [Fact(DisplayName = "Trying to read OPF package without href attribute in a manifest item XML node should fail with EpubPackageException")] + public async void ReadPackageWithoutManifestItemHrefTest() + { + await TestFailingReadOperation(OPF_FILE_WITHOUT_HREF_IN_MANIFEST_ITEM); + } + + [Fact(DisplayName = "Trying to read OPF package with empty href attribute in a manifest item XML node should fail with EpubPackageException")] + public async void ReadPackageWithEmptyManifestItemHrefTest() + { + await TestFailingReadOperation(OPF_FILE_WITH_EMPTY_HREF_IN_MANIFEST_ITEM); + } + + [Fact(DisplayName = "Trying to read OPF package without href attribute in a manifest item XML node with SkipInvalidManifestItems = true should succeed")] + public async void ReadPackageWithoutManifestItemHrefWithSkippingInvalidManifestItemsTest() + { + await TestSuccessfulReadOperationWithSkippingInvalidManifestItems(OPF_FILE_WITHOUT_HREF_IN_MANIFEST_ITEM, MinimalEpub3Package); + } + + [Fact(DisplayName = "Trying to read OPF package without media type attribute in a manifest item XML node should fail with EpubPackageException")] + public async void ReadPackageWithoutManifestItemMediaTypeTest() + { + await TestFailingReadOperation(OPF_FILE_WITHOUT_MEDIA_TYPE_IN_MANIFEST_ITEM); + } + + [Fact(DisplayName = "Trying to read OPF package with empty media type attribute in a manifest item XML node should fail with EpubPackageException")] + public async void ReadPackageWithEmptyManifestItemMediaTypeTest() + { + await TestFailingReadOperation(OPF_FILE_WITH_EMPTY_MEDIA_TYPE_IN_MANIFEST_ITEM); + } + + [Fact(DisplayName = "Trying to read OPF package without media type attribute in a manifest item XML node with SkipInvalidManifestItems = true should succeed")] + public async void ReadPackageWithoutManifestItemMediaTypeWithSkippingInvalidManifestItemsTest() + { + await TestSuccessfulReadOperationWithSkippingInvalidManifestItems(OPF_FILE_WITHOUT_MEDIA_TYPE_IN_MANIFEST_ITEM, MinimalEpub3Package); + } + + [Fact(DisplayName = "Trying to read EPUB 2 OPF package without toc attribute in the spine XML node should fail with EpubPackageException")] + public async void ReadEpub2PackageWithoutSpineTocTest() + { + await TestFailingReadOperation(EPUB2_OPF_FILE_WITHOUT_SPINE_TOC); + } + + [Fact(DisplayName = "Trying to read EPUB 2 OPF package with empty toc attribute in the spine XML node should fail with EpubPackageException")] + public async void ReadEpub2PackageWithEmptySpineTocTest() + { + await TestFailingReadOperation(EPUB2_OPF_FILE_WITH_EMPTY_SPINE_TOC); + } + + [Fact(DisplayName = "Trying to read EPUB 2 OPF package without toc attribute in the spine XML node with IgnoreMissingToc = true should succeed")] + public async void ReadEpub2PackageWithoutSpineTocWithIgnoreMissingTocTest() + { + EpubReaderOptions epubReaderOptions = new() + { + PackageReaderOptions = new PackageReaderOptions() + { + IgnoreMissingToc = true + } + }; + await TestSuccessfulReadOperation(EPUB2_OPF_FILE_WITHOUT_SPINE_TOC, Epub2PackageWithoutSpineToc, epubReaderOptions); + } + + [Fact(DisplayName = "Trying to read OPF package without ID ref attribute in a spine item ref XML node should fail with EpubPackageException")] + public async void ReadPackageWithoutSpineItemRefIdRefTest() + { + await TestFailingReadOperation(OPF_FILE_WITHOUT_IDREF_IN_SPINE_ITEMREF); + } + + [Fact(DisplayName = "Trying to read OPF package with empty ID ref attribute in a spine item ref XML node should fail with EpubPackageException")] + public async void ReadPackageWithEmptySpineItemRefIdRefTest() + { + await TestFailingReadOperation(OPF_FILE_WITH_EMPTY_IDREF_IN_SPINE_ITEMREF); + } + + [Fact(DisplayName = "Trying to read OPF package without type attribute in a guide reference XML node should fail with EpubPackageException")] + public async void ReadPackageWithoutGuideReferenceTypeTest() + { + await TestFailingReadOperation(OPF_FILE_WITHOUT_TYPE_IN_GUIDE_REFERENCE); + } + + [Fact(DisplayName = "Trying to read OPF package with empty type attribute in a guide reference XML node should fail with EpubPackageException")] + public async void ReadPackageWithEmptyGuideReferenceTypeTest() + { + await TestFailingReadOperation(OPF_FILE_WITH_EMPTY_TYPE_IN_GUIDE_REFERENCE); + } + + [Fact(DisplayName = "Trying to read OPF package without href attribute in a guide reference XML node should fail with EpubPackageException")] + public async void ReadPackageWithoutGuideReferenceHrefTest() + { + await TestFailingReadOperation(OPF_FILE_WITHOUT_HREF_IN_GUIDE_REFERENCE); + } + + [Fact(DisplayName = "Trying to read OPF package with empty href attribute in a guide reference XML node should fail with EpubPackageException")] + public async void ReadPackageWithEmptyGuideReferenceHrefTest() + { + await TestFailingReadOperation(OPF_FILE_WITH_EMPTY_HREF_IN_GUIDE_REFERENCE); + } + + private async Task TestSuccessfulReadOperation(string opfFileContent, EpubPackage expectedEpubPackage, EpubReaderOptions epubReaderOptions = null) + { + TestZipFile testZipFile = CreateTestZipFileWithOpfFile(opfFileContent); + EpubPackage actualEpubPackage = await PackageReader.ReadPackageAsync(testZipFile, OPF_FILE_PATH, epubReaderOptions ?? new EpubReaderOptions()); CompareEpubPackages(expectedEpubPackage, actualEpubPackage); } + private Task TestSuccessfulReadOperationWithSkippingInvalidManifestItems(string opfFileContent, EpubPackage expectedEpubPackage) + { + EpubReaderOptions epubReaderOptions = new() + { + PackageReaderOptions = new PackageReaderOptions() + { + SkipInvalidManifestItems = true + } + }; + return TestSuccessfulReadOperation(opfFileContent, expectedEpubPackage, epubReaderOptions); + } + + private async Task TestFailingReadOperation(string opfFileContent) + { + TestZipFile testZipFile = CreateTestZipFileWithOpfFile(opfFileContent); + await Assert.ThrowsAsync(() => PackageReader.ReadPackageAsync(testZipFile, OPF_FILE_PATH, new EpubReaderOptions())); + } + + private TestZipFile CreateTestZipFileWithOpfFile(string opfFileContent) + { + TestZipFile testZipFile = new(); + testZipFile.AddEntry(CONTAINER_FILE_PATH, CONTAINER_FILE); + testZipFile.AddEntry(OPF_FILE_PATH, opfFileContent); + return testZipFile; + } + private void CompareEpubPackages(EpubPackage expected, EpubPackage actual) { Assert.NotNull(actual); diff --git a/Source/VersOne.Epub/Readers/PackageReader.cs b/Source/VersOne.Epub/Readers/PackageReader.cs index a19fbbd..b74c28a 100644 --- a/Source/VersOne.Epub/Readers/PackageReader.cs +++ b/Source/VersOne.Epub/Readers/PackageReader.cs @@ -26,6 +26,10 @@ public static async Task ReadPackageAsync(IZipFile epubFile, string } XNamespace opfNamespace = "http://www.idpf.org/2007/opf"; XElement packageNode = containerDocument.Element(opfNamespace + "package"); + if (packageNode == null) + { + throw new EpubPackageException("EPUB parsing error: package XML element not found in the package file."); + } EpubPackage result = new EpubPackage(); string epubVersionValue = packageNode.Attribute("version").Value; EpubVersion epubVersion;