From 616c359cab363e2ac4b1f3938927a9de661db12e Mon Sep 17 00:00:00 2001 From: "halil.cakal" Date: Thu, 9 Feb 2023 15:51:57 +0000 Subject: [PATCH] Test for yang file archive limitation - add new test - fixed misleading method names Issue-ID: CPS-1480 Change-Id: I9dd8f41d0acfc8b1c063aab65567d619670f4905 Signed-off-by: halil.cakal --- .../org/onap/cps/rest/utils/MultipartFileUtil.java | 8 ++-- .../onap/cps/rest/utils/ZipFileSizeValidator.java | 26 +++++++----- .../cps/rest/utils/MultipartFileUtilSpec.groovy | 31 +++++++++++++- .../cps/rest/utils/ZipFileSizeValidatorSpec.groovy | 45 +++++++++++---------- .../resources/yang-files-set-total-1083-bytes.zip | Bin 0 -> 858 bytes 5 files changed, 72 insertions(+), 38 deletions(-) create mode 100644 cps-rest/src/test/resources/yang-files-set-total-1083-bytes.zip diff --git a/cps-rest/src/main/java/org/onap/cps/rest/utils/MultipartFileUtil.java b/cps-rest/src/main/java/org/onap/cps/rest/utils/MultipartFileUtil.java index 534077caa..3e01f6ee8 100644 --- a/cps-rest/src/main/java/org/onap/cps/rest/utils/MultipartFileUtil.java +++ b/cps-rest/src/main/java/org/onap/cps/rest/utils/MultipartFileUtil.java @@ -2,6 +2,7 @@ * ============LICENSE_START======================================================= * Copyright (C) 2020 Pantheon.tech * Modifications Copyright (C) 2021 Bell Canada. + * Modifications Copyright (C) 2023 Nordix Foundation. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -127,16 +128,17 @@ public class MultipartFileUtil { } private static String extractYangResourceContent(final ZipInputStream zipInputStream, - final ZipFileSizeValidator zipFileSizeValidator) throws IOException { + final ZipFileSizeValidator zipFileSizeValidator) + throws IOException { try (final var byteArrayOutputStream = new ByteArrayOutputStream()) { var totalSizeEntry = 0; int numberOfBytesRead; final var buffer = new byte[READ_BUFFER_SIZE]; - zipFileSizeValidator.incrementTotalEntryInArchive(); + zipFileSizeValidator.incrementTotalYangFileEntryCountInArchive(); while ((numberOfBytesRead = zipInputStream.read(buffer, 0, READ_BUFFER_SIZE)) > 0) { byteArrayOutputStream.write(buffer, 0, numberOfBytesRead); totalSizeEntry += numberOfBytesRead; - zipFileSizeValidator.updateTotalSizeArchive(numberOfBytesRead); + zipFileSizeValidator.updateTotalUncompressedSizeOfYangFilesInArchive(numberOfBytesRead); zipFileSizeValidator.validateCompresssionRatio(totalSizeEntry); } return byteArrayOutputStream.toString(StandardCharsets.UTF_8); diff --git a/cps-rest/src/main/java/org/onap/cps/rest/utils/ZipFileSizeValidator.java b/cps-rest/src/main/java/org/onap/cps/rest/utils/ZipFileSizeValidator.java index d148fb70d..2e303d1d4 100644 --- a/cps-rest/src/main/java/org/onap/cps/rest/utils/ZipFileSizeValidator.java +++ b/cps-rest/src/main/java/org/onap/cps/rest/utils/ZipFileSizeValidator.java @@ -1,6 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2021 Bell Canada. + * Modifications Copyright (C) 2023 Nordix Foundation. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,31 +29,33 @@ import org.onap.cps.spi.exceptions.ModelValidationException; public class ZipFileSizeValidator { private static final int THRESHOLD_ENTRIES = 10000; - private static final int THRESHOLD_SIZE = 100000000; + private static int THRESHOLD_SIZE = 100000000; private static final double THRESHOLD_RATIO = 40; private static final String INVALID_ZIP = "Invalid ZIP archive content."; - private int totalSizeArchive = 0; - private int totalEntryInArchive = 0; + private int totalUncompressedSizeOfYangFilesInArchive = 0; + private int totalYangFileEntriesInArchive = 0; private long compressedSize = 0; /** * Increment the totalEntryInArchive by 1. */ - public void incrementTotalEntryInArchive() { - totalEntryInArchive++; + public void incrementTotalYangFileEntryCountInArchive() { + totalYangFileEntriesInArchive++; } /** * Update the totalSizeArchive by numberOfBytesRead. + * * @param numberOfBytesRead the number of bytes of each entry */ - public void updateTotalSizeArchive(final int numberOfBytesRead) { - totalSizeArchive += numberOfBytesRead; + public void updateTotalUncompressedSizeOfYangFilesInArchive(final int numberOfBytesRead) { + totalUncompressedSizeOfYangFilesInArchive += numberOfBytesRead; } /** * Validate the total Compression size of the zip. + * * @param totalEntrySize the size of the unzipped entry. */ public void validateCompresssionRatio(final int totalEntrySize) { @@ -68,13 +71,14 @@ public class ZipFileSizeValidator { * Validate the total Size and number of entries in the zip. */ public void validateSizeAndEntries() { - if (totalSizeArchive > THRESHOLD_SIZE) { + if (totalUncompressedSizeOfYangFilesInArchive > THRESHOLD_SIZE) { throw new ModelValidationException(INVALID_ZIP, - String.format("The uncompressed data size exceeds the CPS limit %s bytes.", THRESHOLD_SIZE)); + String.format("The total size of uncompressed yang files exceeds the CPS limit of %s bytes.", + THRESHOLD_SIZE)); } - if (totalEntryInArchive > THRESHOLD_ENTRIES) { + if (totalYangFileEntriesInArchive > THRESHOLD_ENTRIES) { throw new ModelValidationException(INVALID_ZIP, - String.format("The number of entries in the archive exceeds the CPS limit %s.", + String.format("The number of yang file entries in the archive exceeds the CPS limit %s.", THRESHOLD_ENTRIES)); } } diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/utils/MultipartFileUtilSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/utils/MultipartFileUtilSpec.groovy index 3f4729ec7..67ee50e89 100644 --- a/cps-rest/src/test/groovy/org/onap/cps/rest/utils/MultipartFileUtilSpec.groovy +++ b/cps-rest/src/test/groovy/org/onap/cps/rest/utils/MultipartFileUtilSpec.groovy @@ -1,6 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2020 Pantheon.tech + * Modifications Copyright (C) 2023 Nordix Foundation. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -51,7 +52,7 @@ class MultipartFileUtilSpec extends Specification { def 'Extract yang resources from zip archive.'() { given: 'uploaded zip archive containing 2 yang files and 1 not yang (json) file' def multipartFile = new MockMultipartFile("file", "TEST.ZIP", "application/zip", - getClass().getResource("/yang-files-set.zip").getBytes()) + getClass().getResource("/yang-files-set.zip").getBytes()) when: 'resources are extracted from zip file' def result = MultipartFileUtil.extractYangResourcesMap(multipartFile) then: 'information from yang files is extracted, not yang file (json) is ignored' @@ -60,6 +61,32 @@ class MultipartFileUtilSpec extends Specification { assert result["component.yang"] == "fake component content 1\n" } + def 'Yang file limits in zip archive: #scenario for the bug reported in CPS-1477'() { + given: 'a yang file size (uncompressed) limit of #threshold bytes' + ZipFileSizeValidator.THRESHOLD_SIZE = threshold + and: 'an archive with a yang file of 1083 bytes' + def multipartFile = multipartZipFileFromResource('/yang-files-set-total-1083-bytes.zip') + when: 'attempt to extract yang files' + def thrownException = null + try { + MultipartFileUtil.extractYangResourcesMap(multipartFile) + } catch (Exception e) { + thrownException = e + } + then: 'ModelValidationException indicating size limit is only thrown when threshold exceeded' + if (thresholdExceeded) { + assert thrownException instanceof ModelValidationException + assert thrownException.details.contains('limit of ' + threshold + ' bytes') + } else { + assert thrownException == null + } + where: + scenario | threshold || thresholdExceeded + 'exceed limit' | 1082 || true + 'equals to limit' | 1083 || false + 'within limit' | 1084 || false + } + def 'Extract resources from zip archive having #caseDescriptor.'() { when: 'attempt to extract resources from zip file is performed' MultipartFileUtil.extractYangResourcesMap(multipartFile) @@ -91,7 +118,7 @@ class MultipartFileUtilSpec extends Specification { def multipartZipFileFromResource(resourcePath) { return new MockMultipartFile("file", "TEST.ZIP", "application/zip", - getClass().getResource(resourcePath).getBytes()) + getClass().getResource(resourcePath).getBytes()) } def multipartFileForIOException(extension) { diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/utils/ZipFileSizeValidatorSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/utils/ZipFileSizeValidatorSpec.groovy index 16fbf9885..60ecb2e3b 100644 --- a/cps-rest/src/test/groovy/org/onap/cps/rest/utils/ZipFileSizeValidatorSpec.groovy +++ b/cps-rest/src/test/groovy/org/onap/cps/rest/utils/ZipFileSizeValidatorSpec.groovy @@ -1,6 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2021 Bell Canada. + * Modifications Copyright (C) 2023 Nordix Foundation. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,25 +33,25 @@ class ZipFileSizeValidatorSpec extends Specification { def compressedFileSize = 100 def setup() { - objectUnderTest.setTotalEntryInArchive(0) - objectUnderTest.setTotalSizeArchive(0) + objectUnderTest.setTotalYangFileEntriesInArchive(0) + objectUnderTest.setTotalUncompressedSizeOfYangFilesInArchive(0) objectUnderTest.setCompressedSize(compressedFileSize) } - def 'Increment the total entries in Archive.'() { - when: 'the totalEntriesInArchive value is incremented' - objectUnderTest.incrementTotalEntryInArchive() - then: 'the totalEntriesInArchive is incremented by 1' - assert objectUnderTest.totalEntryInArchive == old(objectUnderTest.totalEntryInArchive) + 1 + def 'Increment the total yang file entry count in Archive.'() { + when: 'the totalYangFileEntryInArchive value is incremented' + objectUnderTest.incrementTotalYangFileEntryCountInArchive() + then: 'the totalYangFileEntryInArchive is incremented by 1' + assert objectUnderTest.totalYangFileEntriesInArchive == old(objectUnderTest.totalYangFileEntriesInArchive) + 1 } - def 'Update the total size of Archive.'() { + def 'Update the total uncompressed size of yang files in Archive.'() { given: 'the size of an entry of archive' def entrySize = 100 - when: 'the totalSizeArchive is to be updated with the latest entry Size' - objectUnderTest.updateTotalSizeArchive(entrySize) - then: 'the totalSizeArchive is updated as expected' - assert objectUnderTest.totalSizeArchive == old(objectUnderTest.totalSizeArchive) + entrySize + when: 'the totalUncompressedSizeOfYangFilesInArchive is to be updated with the latest entry Size' + objectUnderTest.updateTotalUncompressedSizeOfYangFilesInArchive(entrySize) + then: 'the totalUncompressedSizeOfYangFilesInArchive is updated as expected' + assert objectUnderTest.totalUncompressedSizeOfYangFilesInArchive == old(objectUnderTest.totalUncompressedSizeOfYangFilesInArchive) + entrySize } def 'Validate the zip archive for compression ratio less that threshold compression ratio.'() { @@ -73,29 +74,29 @@ class ZipFileSizeValidatorSpec extends Specification { def 'Validate the zip archive for thresholdSize and thresholdEntries #caseDescriptor.'() { given: - objectUnderTest.setTotalEntryInArchive(totalEntriesInArchive) - objectUnderTest.setTotalSizeArchive(totalSizeArchive) + objectUnderTest.setTotalYangFileEntriesInArchive(totalYangEntriesInArchive) + objectUnderTest.setTotalUncompressedSizeOfYangFilesInArchive(totalUncompressedSizeofYangArchive) when: 'the validation is performed against the threshold size and threshold Entries count' objectUnderTest.validateSizeAndEntries() then: 'validation passes and no exception is thrown' noExceptionThrown() where: 'following cases are tested' - caseDescriptor | totalSizeArchive | totalEntriesInArchive - 'less than threshold value' | thresholdSize - 1 | thresholdEntries - 1 - 'at threshold value' | thresholdSize | thresholdEntries + caseDescriptor | totalUncompressedSizeofYangArchive | totalYangEntriesInArchive + 'less than threshold value' | thresholdSize - 1 | thresholdEntries - 1 + 'at threshold value' | thresholdSize | thresholdEntries } def 'Validate the zip archive for thresholdSize and thresholdEntries with #caseDescriptor.'() { given: - objectUnderTest.setTotalEntryInArchive(totalEntriesInArchive) - objectUnderTest.setTotalSizeArchive(totalSizeArchive) + objectUnderTest.setTotalYangFileEntriesInArchive(totalYangEntriesInArchive) + objectUnderTest.setTotalUncompressedSizeOfYangFilesInArchive(totalUncompressedSizeofYangArchive) when: 'the validation is performed against the threshold size and threshold Entries count' objectUnderTest.validateSizeAndEntries() then: 'validation fails and exception is thrown' thrown ModelValidationException where: 'following cases are tested' - caseDescriptor | totalSizeArchive | totalEntriesInArchive - 'totalEntriesInArchive exceeds threshold value' | thresholdSize | thresholdEntries + 1 - 'totalSizeArchive exceeds threshold value' | thresholdSize + 1 | thresholdEntries + caseDescriptor | totalUncompressedSizeofYangArchive | totalYangEntriesInArchive + 'totalEntriesInArchive exceeds threshold value' | thresholdSize | thresholdEntries + 1 + 'totalSizeArchive exceeds threshold value' | thresholdSize + 1 | thresholdEntries } } diff --git a/cps-rest/src/test/resources/yang-files-set-total-1083-bytes.zip b/cps-rest/src/test/resources/yang-files-set-total-1083-bytes.zip new file mode 100644 index 0000000000000000000000000000000000000000..9908055e31ca5222f11c4de4888863011aab346c GIT binary patch literal 858 zcmWIWW@Zs#0D;nYlOQkyO7H{e%EY{M-L%Y{)MDM@)DrywsA3L=PqkHn+on%`5Drvy z28d-a6(<%Kr{*T*RO)3F=jXMawdOjYAi#3rUa-!!ldMJ3i=#OT&3~+HDnCDqpF?b# z@#h3R)&E}+#643R+BE2=~ZNdVa(jL$(JU!^5xrz*glsF0kWSCX1nqF~5{@S@H{ z&#;<`$$`E=)09~l7-WGoT6iSq=N9DW0X6D@B;KCdDA=GN;BxVf);5;HoP$n&wpRrC z*v&7zn`$Pp=zsI`ry)9;aVM|*n7{E#>E**Z5v@#WM-KfF6mk5tO!Cgr2fq@J{0Vx0 zOgev$-#^9xZ$>7223#?t0(1)q2tX)wcX2T&fF&3~LBZX*NBR9VMvsIGPd3=xgwqHM zu*D|K0+2bnK#b;bSiphf7R9zX>Sf$Vt}&WCJnpv2hCJhp*cfnr-_*Iba_ z9=xy!!L$t&cpMCv(THrD03(LqP$L!DI>z3{#UN{J*L+-xY8@*Z&@KiRAY219a0U=F GFaQ8{I{N_t literal 0 HcmV?d00001 -- 2.16.6