diff --git a/.gitignore b/.gitignore index 83276d7..425bb75 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ .project !.gitignore # except for .gitignore .settings +/out *.tmproj diff --git a/README.md b/README.md index 2d3acb2..4bb3f6f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# File-Uploader Plugin (Latest 3.0.9) +# File-Uploader Plugin (Latest 3.1.0) Supported Grails 3.2.0 @@ -54,12 +54,21 @@ fileuploader { allowedExtensions = ["xls"] path = "./web-app/degree-applications" storageTypes = "" + //Explnation about this congiguration is given below + checksum { + calculate = true + algorithm = Algorithm.MD5 + } } userAvatar { maxSize = 1024 * 1024 * 2 //256 kbytes allowedExtensions = ["jpg","jpeg","gif","png"] storageTypes = "CDN" container = "anyContainerName" + checksum { + calculate = false + algorithm = Algorithm.SHA1 + } } logo { maxSize = 1024 * 1024 * 2 //256 kbytes @@ -87,3 +96,26 @@ can be overwritten for group level configuration by setting **expirationPeriod** ``` # Google Default Credentials export GOOGLE_APPLICATION_CREDENTIALS='/path/to/key.json' +4. To Enable checksum checks and generation, define configuration as shown in the sample configuration, +``` + groups { + degreeApplication { // Non CDN files, will be stored in local directory. + maxSize = 1000 * 1024 //256 kbytes + allowedExtensions = ["xls"] + path = "./web-app/degree-applications" + storageTypes = "" + checksum { + calculate = true + algorithm = Algorithm.MD5 + } + } + } + +``` +If first flag is set to true, plugin will generate checksum for the uploaded file and try to find a file from database having same checksum. If any such file is found, then plugin will throw an **DuplicateFileException** exception. +Second flag will tell plugin which algorithm to use to calculate the checksum. Currently, possible choices are, +``` +Algorithm.MD5 and, +Algorithm.SHA1 +``` +Second flag will be ignored if first flag is set to false. By default checksum calculation features is disabled. To enable provide valid configurations. \ No newline at end of file diff --git a/build.gradle b/build.gradle index 55283f1..7a6efcd 100644 --- a/build.gradle +++ b/build.gradle @@ -18,12 +18,12 @@ buildscript { mavenCentral() } dependencies { - classpath "com.causecode.plugins:gradle-code-quality:1.0.0" + classpath "com.causecode.plugins:gradle-code-quality:1.0.1-RC1" classpath "org.grails:grails-gradle-plugin:$grailsVersion" } } -version "3.0.9" +version "3.1.0" group "com.causecode.plugins" apply plugin: "idea" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2567a49..e0f4195 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Nov 28 17:45:47 IST 2017 +#Tue Jan 30 19:18:03 IST 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.4.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.4.1-all.zip diff --git a/grails-app/domain/com/causecode/fileuploader/UFile.groovy b/grails-app/domain/com/causecode/fileuploader/UFile.groovy index b9cebe8..57df071 100644 --- a/grails-app/domain/com/causecode/fileuploader/UFile.groovy +++ b/grails-app/domain/com/causecode/fileuploader/UFile.groovy @@ -34,6 +34,12 @@ class UFile implements Serializable { String name String path + //Contains calculated hash value of file content + String checksum + + //Algorithm Used to calculate Checksum + String checksumAlgorithm + UFileType type Date dateCreated @@ -48,12 +54,19 @@ class UFile implements Serializable { name blank: false fileGroup blank: false provider nullable: true + checksum nullable: true + checksumAlgorithm nullable: true dateCreated bindable: false lastUpdated bindable: false } static mapping = { path sqlType: 'text' + + //Index on checksum + checksum index: true + //Index on checksumAlgorithm + checksumAlgorithm index: true } def afterDelete() { @@ -118,6 +131,7 @@ enum UFileType { LOCAL(3) final int id + UFileType(int id) { this.id = id } @@ -137,6 +151,7 @@ enum CDNProvider { LOCAL(4) final int id + CDNProvider(int id) { this.id = id } diff --git a/grails-app/services/com/causecode/fileuploader/FileUploaderService.groovy b/grails-app/services/com/causecode/fileuploader/FileUploaderService.groovy index 56d7c0e..8836a10 100644 --- a/grails-app/services/com/causecode/fileuploader/FileUploaderService.groovy +++ b/grails-app/services/com/causecode/fileuploader/FileUploaderService.groovy @@ -8,18 +8,21 @@ package com.causecode.fileuploader import com.causecode.fileuploader.cdn.CDNFileUploader +import com.causecode.fileuploader.cdn.amazon.AmazonCDNFileUploaderImpl import com.causecode.fileuploader.util.FileUploaderUtils +import com.causecode.fileuploader.util.Time +import com.causecode.fileuploader.util.checksum.ChecksumValidator +import com.causecode.fileuploader.util.checksum.exceptions.DuplicateFileException import com.causecode.util.NucleusUtils import grails.core.GrailsApplication import grails.util.Holders import groovy.io.FileType +import org.apache.commons.validator.UrlValidator import org.springframework.context.MessageSource import org.springframework.dao.DataIntegrityViolationException import org.springframework.web.multipart.MultipartFile + import java.nio.channels.FileChannel -import org.apache.commons.validator.UrlValidator -import com.causecode.fileuploader.cdn.amazon.AmazonCDNFileUploaderImpl -import com.causecode.fileuploader.util.Time /** * A service class for all fileUpload related operations. @@ -72,15 +75,28 @@ class FileUploaderService { * @return */ UFile saveFile(String group, def file, String customFileName = '', Object userInstance = null, Locale locale = null) - throws StorageConfigurationException, UploadFailureException, ProviderNotFoundException { - + throws StorageConfigurationException, UploadFailureException, + ProviderNotFoundException, FileNotFoundException, + DuplicateFileException { Date expireOn long currentTimeMillis = System.currentTimeMillis() CDNProvider cdnProvider UFileType type = UFileType.LOCAL String path - FileGroup fileGroupInstance = new FileGroup(group) + ChecksumValidator checksumValidator = new ChecksumValidator(fileGroupInstance) + + if (checksumValidator.shouldCalculateChecksum()) { + UFile uFileInstance = UFile.findByChecksumAndChecksumAlgorithm(checksumValidator.getChecksum(file), + checksumValidator.algorithm) + if (uFileInstance) { + throw new DuplicateFileException( + "Checksum for file ${file.name} is ${checksumValidator.getChecksum(file)} and " + + "that checksum refers to an existing file ${uFileInstance} on server" + ) + } + } + Map fileData = fileGroupInstance.getFileNameAndExtensions(file, customFileName) if (fileData.empty || !file) { @@ -89,35 +105,27 @@ class FileUploaderService { fileGroupInstance.allowedExtensions(fileData, locale, group) fileGroupInstance.validateFileSize(fileData.fileSize, locale) - // If group specific storage type is not defined then use the common storage type String storageTypes = fileGroupInstance.groupConfig.storageTypes ?: fileGroupInstance.config.storageTypes if (storageTypes == 'CDN') { type = UFileType.CDN_PUBLIC - fileGroupInstance.scopeFileName(userInstance, fileData, group, currentTimeMillis) long expirationPeriod = getExpirationPeriod(group) - File tempFile if (file instanceof File) { - /* No need to transfer a file of type File since its already in a temporary location. - * (Saves resource utilization) - */ + // No need to transfer a file of type File since its already in a temporary location. tempFile = file } else { if (file instanceof MultipartFile) { - tempFile = new File(newTemporaryDirectoryPath + - "${fileData.fileName}.${fileData.fileExtension}") - + tempFile = getTempFilePathForMultipartFile(fileData.fileName, fileData.fileExtension) file.transferTo(tempFile) } } // Delete the temporary file when JVM exited since the base file is not required after upload tempFile.deleteOnExit() - cdnProvider = fileGroupInstance.cdnProvider if (!cdnProvider) { @@ -125,27 +133,33 @@ class FileUploaderService { } expireOn = isPublicGroup(group) ? null : new Date(new Date().time + expirationPeriod * 1000) - path = uploadFileToCloud(fileData, fileGroupInstance, tempFile) - } else { path = fileGroupInstance.getLocalSystemPath(storageTypes, fileData, currentTimeMillis) - - // Move file log.debug "Moving [$fileData.fileName] to [${path}]." moveFile(file, path) } - UFile ufile = new UFile([name: fileData.fileName, size: fileData.fileSize, path: path, type: type, - extension: fileData.fileExtension, expiresOn: expireOn, fileGroup: group, provider: cdnProvider]) - NucleusUtils.save(ufile, true) + UFile ufile = new UFile( + [name : fileData.fileName, size: fileData.fileSize, path: path, type: type, + extension: fileData.fileExtension, expiresOn: expireOn, fileGroup: group, provider: cdnProvider]) + + if (checksumValidator.shouldCalculateChecksum()) { + ufile.checksum = checksumValidator.getChecksum(file) + ufile.checksumAlgorithm = checksumValidator.algorithm + } + NucleusUtils.save(ufile, true) return ufile } + private File getTempFilePathForMultipartFile(String fileName, String fileExtension) { + return new File(newTemporaryDirectoryPath + "${fileName}.${fileExtension}") + } + /** * Method is used to upload file to cloud provider. Then it gets the path of uploaded file - * @params fileData, fileGroupInstance, tempFile + * @params fileData , fileGroupInstance, tempFile * @return path of uploaded file * */ @@ -176,7 +190,7 @@ class FileUploaderService { /** * Method is used to move file from temp directory to another. - * @params fileInstance, path + * @params fileInstance , path * */ void moveFile(def file, String path) { diff --git a/src/main/groovy/com/causecode/fileuploader/StorageException.groovy b/src/main/groovy/com/causecode/fileuploader/StorageException.groovy index 3d87cf7..b5462fc 100644 --- a/src/main/groovy/com/causecode/fileuploader/StorageException.groovy +++ b/src/main/groovy/com/causecode/fileuploader/StorageException.groovy @@ -3,7 +3,7 @@ * All rights reserved. * * Redistribution and use in source and binary forms, with or - * without modification, are not permitted. +* without modification, are not permitted. */ package com.causecode.fileuploader diff --git a/src/main/groovy/com/causecode/fileuploader/util/checksum/Algorithm.groovy b/src/main/groovy/com/causecode/fileuploader/util/checksum/Algorithm.groovy new file mode 100644 index 0000000..606e159 --- /dev/null +++ b/src/main/groovy/com/causecode/fileuploader/util/checksum/Algorithm.groovy @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2011 - Present, CauseCode Technologies Pvt Ltd, India. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are not permitted. + */ +package com.causecode.fileuploader.util.checksum + +/** + * Enum to represent Algorithm to be used with hash calculation + * @author Milan Savaliya + * @since 3.1.0 + */ +enum Algorithm { + MD5, + SHA1 +} diff --git a/src/main/groovy/com/causecode/fileuploader/util/checksum/ChecksumConfig.groovy b/src/main/groovy/com/causecode/fileuploader/util/checksum/ChecksumConfig.groovy new file mode 100644 index 0000000..1bc255c --- /dev/null +++ b/src/main/groovy/com/causecode/fileuploader/util/checksum/ChecksumConfig.groovy @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2011 - Present, CauseCode Technologies Pvt Ltd, India. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are not permitted. + */ +package com.causecode.fileuploader.util.checksum + +/** + * Checksum Config object to take decision weather to calculate checksum/hash or not, and if to calculate then with + * which algorithm + * @author Milan Savaliya + * @since 3.1.0 + */ + +class ChecksumConfig { + boolean calculate = false + Algorithm algorithm = Algorithm.MD5 +} diff --git a/src/main/groovy/com/causecode/fileuploader/util/checksum/ChecksumValidator.groovy b/src/main/groovy/com/causecode/fileuploader/util/checksum/ChecksumValidator.groovy new file mode 100644 index 0000000..7d64b88 --- /dev/null +++ b/src/main/groovy/com/causecode/fileuploader/util/checksum/ChecksumValidator.groovy @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2011 - Present, CauseCode Technologies Pvt Ltd, India. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are not permitted. + */ +package com.causecode.fileuploader.util.checksum + +import com.causecode.fileuploader.FileGroup +import com.causecode.fileuploader.util.checksum.beans.FileInputBean +import com.causecode.fileuploader.util.checksum.beans.MultipartFileInputBeanImpl +import com.causecode.fileuploader.util.checksum.beans.SimpleFileInputBeanImpl +import com.causecode.fileuploader.util.checksum.exceptions.UnRecognizedFileTypeException +import org.springframework.web.multipart.MultipartFile + +/** + * Helper class to validate and generate checksum. This class will communicate with service to + * generate checksum + * @author Milan Savaliya + * @since 3.1.0 + */ +class ChecksumValidator { + + /** + * Member variable to store the calculated hash code + */ + private String calculatedChecksum = null + private final ChecksumConfig checksumConfig + private final FileGroup fileGroup + + ChecksumValidator(FileGroup fileGroup) { + this.fileGroup = fileGroup + this.checksumConfig = getChecksumConfig(fileGroup) + } + + /** + * returns if flag to calculate checksum is set or not. + * @return boolean + */ + boolean shouldCalculateChecksum() { + return this.checksumConfig.calculate + } + + /** + * Method calculates the checksum and returns calculated checksum. This method does not recalculate + * checksum on second call. + * This method returns previously calculated checksum in the next subsequent calls. + * @param file + * @return String + * @throws FileNotFoundException + */ + String getChecksum(def file) throws FileNotFoundException { + calculatedChecksum = (calculatedChecksum ?: this.getChecksumForFile(file)) + return calculatedChecksum + } + + /** + * Returns the String representation of the algorithm being used to calculate the checksum + * @return String + */ + String getAlgorithm() { + return this.checksumConfig.algorithm.toString() + } + + /** + * Private method to get the ChecksumConfig object from the given fileGroup object. + * @param fileGroup + * @return ChecksumConfig + */ + private static ChecksumConfig getChecksumConfig(FileGroup fileGroup) { + Map checksumProperties = fileGroup.groupConfig.checksum + if (!checksumProperties) { + return new ChecksumConfig() + } + + boolean calculate = checksumProperties.calculate ?: false + Algorithm algorithm = checksumProperties.algorithm ?: Algorithm.MD5 + return new ChecksumConfig(calculate: calculate, algorithm: algorithm) + } + + /** + * This is actual heart method which generates the checksum and returns its hex string representation + * to the calling end. + * @param file + * @return String + * @throws FileNotFoundException + */ + private String getChecksumForFile(def file) throws FileNotFoundException { + FileInputBean fileInputBean = getFileInputBeanForFile(file) + HashCalculator hashCalculator = new FileHashCalculator(fileInputBean, this.checksumConfig.algorithm) + return hashCalculator.calculateHash() + } + + /** + * Generates the FileInputBean from the given File instance. Currently this method accepts File or + * MultipartFile Instance + * @param file + * @return FileInputBean + * @throws UnRecognizedFileTypeException + */ + private static FileInputBean getFileInputBeanForFile(def file) throws UnRecognizedFileTypeException { + if (file in File) { + return new SimpleFileInputBeanImpl(file) + } else if (file in MultipartFile) { + return new MultipartFileInputBeanImpl(file) + } + + throw new UnRecognizedFileTypeException("${file.class.name} is not recognized for FileInputBean") + } +} diff --git a/src/main/groovy/com/causecode/fileuploader/util/checksum/FileHashCalculator.groovy b/src/main/groovy/com/causecode/fileuploader/util/checksum/FileHashCalculator.groovy new file mode 100644 index 0000000..caca380 --- /dev/null +++ b/src/main/groovy/com/causecode/fileuploader/util/checksum/FileHashCalculator.groovy @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2011 - Present, CauseCode Technologies Pvt Ltd, India. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are not permitted. + */ +package com.causecode.fileuploader.util.checksum + +import com.causecode.fileuploader.util.checksum.beans.FileInputBean +import groovy.util.logging.Slf4j + +import javax.xml.bind.annotation.adapters.HexBinaryAdapter +import java.security.MessageDigest + +/** + * This class is the Utility Class which will be used to calculate the Hash of the File + * This class works on the byte level layer. + * @author Milan Savaliya + * @since 3.1.0 + */ +@Slf4j +class FileHashCalculator implements HashCalculator { + + //FileInputBean instance + private final FileInputBean fileInputBean + + /** + * Constructs FileHashCalculator instance and throws FileNotFoundException If Input File Is null or not exists + * This uses default MD5 Hash Calculating Algorithm + * @param algorithm + * @param fileInputBean + * @throws FileNotFoundException + */ + FileHashCalculator(FileInputBean fileInputBean) throws FileNotFoundException { + this.fileInputBean = fileInputBean + validateInputs() + } + + /** + * Constructs FileHashCalculator with given Algorithm instance + * @param fileInputBean + * @param algorithm + */ + FileHashCalculator(FileInputBean fileInputBean, Algorithm algorithm) { + this(fileInputBean) + this.algorithm = algorithm + } + + /** + * This Method validates inputs. + */ + private void validateInputs() throws FileNotFoundException { + if (!fileInputBean) { + throw new FileNotFoundException('File not found') + } + } + + /** + * This method calculates the hash and returns a hex String representation of Calculated Hash. + * @return String [ Calculated Hash ] + */ + @Override + String calculateHash() { + log.info "Starting checksum calculation For File ${fileInputBean.name}" + MessageDigest messageDigest = MessageDigest.getInstance(this.algorithm.toString()) + String hexHasString = new HexBinaryAdapter().marshal(messageDigest.digest(this.fileInputBean.bytes)) + log.info "Calculated Checksum is:- ${hexHasString}" + + return hexHasString + } +} diff --git a/src/main/groovy/com/causecode/fileuploader/util/checksum/HashCalculator.groovy b/src/main/groovy/com/causecode/fileuploader/util/checksum/HashCalculator.groovy new file mode 100644 index 0000000..b8bd783 --- /dev/null +++ b/src/main/groovy/com/causecode/fileuploader/util/checksum/HashCalculator.groovy @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2011 - Present, CauseCode Technologies Pvt Ltd, India. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are not permitted. + */ +package com.causecode.fileuploader.util.checksum + +/** + * Trait Specifying Hash Calculating Behaviour + * @author Milan Savaliya + * @since 3.1.0 + */ +trait HashCalculator { + + /** + * Instance of an Algorithm which would be used to calculate the hashF + * Default is set to the "MD5" + */ + private Algorithm algorithm = Algorithm.MD5 + + /** + * Setter for algorithm + */ + void setAlgorithm(Algorithm algorithm) { + this.algorithm = algorithm + } + + /** + * This method returns an instance of a algorithm which will be used to calculate the checksum + * @return + */ + Algorithm getAlgorithm() { + return this.algorithm + } + + /** + * This method returns an String Representation of the Calculated Hash + * @return + */ + abstract String calculateHash() +} diff --git a/src/main/groovy/com/causecode/fileuploader/util/checksum/beans/FileInputBean.groovy b/src/main/groovy/com/causecode/fileuploader/util/checksum/beans/FileInputBean.groovy new file mode 100644 index 0000000..71e3433 --- /dev/null +++ b/src/main/groovy/com/causecode/fileuploader/util/checksum/beans/FileInputBean.groovy @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2011 - Present, CauseCode Technologies Pvt Ltd, India. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are not permitted. + */ +package com.causecode.fileuploader.util.checksum.beans + +/** + * File InputBean to work with Hash Calculation + * @author Milan Savaliya + * @since 3.1.0 + */ +interface FileInputBean { + /** + * Return the name of the parameter in the multipart form. + * @return the name of the parameter (never {@code null} or empty) + */ + String getName() + + /** + * Return the original filename in the client's filesystem. + *

This may contain path information depending on the browser used, + * but it typically will not with any other than Opera. + * @return the original filename, or the empty String if no fileInputBean + * has been chosen in the multipart form, or {@code null} + * if not defined or not available + */ + String getOriginalFilename() + + /** + * Return the content type of the fileInputBean. + * @return the content type, or {@code null} if not defined + * (or no fileInputBean has been chosen in the multipart form) + */ + String getContentType() throws IOException + + /** + * Return whether the uploaded fileInputBean is empty, that is, either no fileInputBean has + * been chosen in the multipart form or the chosen fileInputBean has no content. + */ + boolean isEmpty() + + /** + * Return the size of the fileInputBean in bytes. + * @return the size of the fileInputBean, or 0 if empty + */ + long getSize() + + /** + * Return the contents of the fileInputBean as an array of bytes. + * @return the contents of the fileInputBean as bytes, or an empty byte array if empty + * @throws IOException in case of access errors (if the temporary store fails) + */ + byte[] getBytes() throws IOException + + /** + * Return an InputStream to read the contents of the fileInputBean from. + * The user is responsible for closing the stream. + * @return the contents of the fileInputBean as stream, or an empty stream if empty + * @throws IOException in case of access errors (if the temporary store fails) + */ + InputStream getInputStream() throws IOException + + /** + * Method to check if file exists on disk or not + * @return + */ + boolean isExists() +} diff --git a/src/main/groovy/com/causecode/fileuploader/util/checksum/beans/MultipartFileInputBeanImpl.groovy b/src/main/groovy/com/causecode/fileuploader/util/checksum/beans/MultipartFileInputBeanImpl.groovy new file mode 100644 index 0000000..4746a53 --- /dev/null +++ b/src/main/groovy/com/causecode/fileuploader/util/checksum/beans/MultipartFileInputBeanImpl.groovy @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2011 - Present, CauseCode Technologies Pvt Ltd, India. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are not permitted. + */ +package com.causecode.fileuploader.util.checksum.beans + +import org.springframework.web.multipart.MultipartFile + +/** + * Bean to handle Multipart File Uploads to calculate Hash/Checksum + * @author Milan Savaliya + * @since 3.1.0 + */ +class MultipartFileInputBeanImpl implements FileInputBean { + + //Instance of MultipartFile Bean + private final MultipartFile multipartFile + + /** + * Constructor to instantiate MultipartFileInputBeanImpl instance + * @param multipartFile + */ + MultipartFileInputBeanImpl(MultipartFile multipartFile) { + this.multipartFile = multipartFile + validateInputs() + } + + /** + * Method checks supplied inputs and throws appropriate exception if input is not in format as expected. + */ + private void validateInputs() throws IllegalArgumentException{ + if (!this.multipartFile) { + throw new IllegalArgumentException('Multipart Instance can not be null') + } + } + + @Override + String getName() { + return this.multipartFile.name + } + + @Override + String getOriginalFilename() { + return this.multipartFile.originalFilename + } + + @Override + String getContentType() { + return this.multipartFile.contentType + } + + @Override + boolean isEmpty() { + return this.multipartFile.isEmpty() + } + + @Override + long getSize() { + return this.multipartFile.size + } + + @Override + byte[] getBytes() throws IOException { + return this.multipartFile.bytes + } + + @Override + InputStream getInputStream() throws IOException { + return this.multipartFile.inputStream + } + + /** + * Whenever There is valid MultipartFile object available, file will surely exists on server. So this method returns + * @return + */ + @Override + boolean isExists() { + return true + } +} diff --git a/src/main/groovy/com/causecode/fileuploader/util/checksum/beans/SimpleFileInputBeanImpl.groovy b/src/main/groovy/com/causecode/fileuploader/util/checksum/beans/SimpleFileInputBeanImpl.groovy new file mode 100644 index 0000000..8dd0189 --- /dev/null +++ b/src/main/groovy/com/causecode/fileuploader/util/checksum/beans/SimpleFileInputBeanImpl.groovy @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2011 - Present, CauseCode Technologies Pvt Ltd, India. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are not permitted. + */ +package com.causecode.fileuploader.util.checksum.beans + +/** + * Bean to handle java.io.File inputs to calculate hash/checksum + * @author Milan Savaliya + * @since 3.1.0 + */ +class SimpleFileInputBeanImpl implements FileInputBean { + + private final File file + + SimpleFileInputBeanImpl(File file) { + this.file = file + validateInputs() + } + + /** + * Method validates supplied inputs and throws appropriate exception if inputs are not in proper format. + * @throws IllegalArgumentException , FileNotFoundException + * */ + private void validateInputs() throws IllegalArgumentException, FileNotFoundException { + if (!this.file) { + throw new IllegalArgumentException('File instance can not be null') + } + + if (!this.file.exists()) { + throw new FileNotFoundException("File with name ${this.file.name} not found") + } + } + + @Override + String getName() { + return this.file.name + } + + @Override + String getOriginalFilename() { + return this.file.name + } + + @Override + String getContentType() throws IOException { + String fileName = this.file.name + if (fileName.lastIndexOf('.') != -1) { + return fileName[fileName.lastIndexOf('.') + 1..fileName.length() - 1] + } + + return null + } + + @Override + boolean isEmpty() { + return this.file.size() == 0 + } + + @Override + long getSize() { + return this.file.size() + } + + @Override + byte[] getBytes() throws IOException { + return this.file.readBytes() + } + + /** + * User needs to close returned input stream. + * @return InputStream + * @throws IOException + */ + @Override + InputStream getInputStream() throws IOException { + return new FileInputStream(this.file) + } + + @Override + boolean isExists() { + return this.file.exists() + } +} diff --git a/src/main/groovy/com/causecode/fileuploader/util/checksum/exceptions/DuplicateFileException.groovy b/src/main/groovy/com/causecode/fileuploader/util/checksum/exceptions/DuplicateFileException.groovy new file mode 100644 index 0000000..58a767e --- /dev/null +++ b/src/main/groovy/com/causecode/fileuploader/util/checksum/exceptions/DuplicateFileException.groovy @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2011 - Present, CauseCode Technologies Pvt Ltd, India. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are not permitted. + */ +package com.causecode.fileuploader.util.checksum.exceptions + +/** + * Exception which will be thrown when calculated checksum with given algorithm founds to be in database + * @author Milan Savaliya + * @since 3.1.0 + */ +class DuplicateFileException extends RuntimeException { + DuplicateFileException(GString gString) { + super(gString.toString()) + } +} diff --git a/src/main/groovy/com/causecode/fileuploader/util/checksum/exceptions/UnRecognizedFileTypeException.groovy b/src/main/groovy/com/causecode/fileuploader/util/checksum/exceptions/UnRecognizedFileTypeException.groovy new file mode 100644 index 0000000..b6f7ae3 --- /dev/null +++ b/src/main/groovy/com/causecode/fileuploader/util/checksum/exceptions/UnRecognizedFileTypeException.groovy @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2011 - Present, CauseCode Technologies Pvt Ltd, India. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are not permitted. + */ +package com.causecode.fileuploader.util.checksum.exceptions + +/** + * Exception will be thrown when FileType for FileInputBean is not valid + * @author Milan Savaliya + * @since 3.1.0 + */ +class UnRecognizedFileTypeException extends RuntimeException { + UnRecognizedFileTypeException(String message) { + super(message) + } +} diff --git a/src/test/groovy/com/causecode/fileuploader/FileUploaderServiceSpec.groovy b/src/test/groovy/com/causecode/fileuploader/FileUploaderServiceSpec.groovy index 50baab1..8d78931 100644 --- a/src/test/groovy/com/causecode/fileuploader/FileUploaderServiceSpec.groovy +++ b/src/test/groovy/com/causecode/fileuploader/FileUploaderServiceSpec.groovy @@ -7,6 +7,8 @@ */ package com.causecode.fileuploader +import com.causecode.fileuploader.util.checksum.Algorithm +import com.causecode.fileuploader.util.checksum.exceptions.DuplicateFileException import grails.buildtestdata.mixin.Build import grails.test.mixin.TestFor import grails.test.runtime.DirtiesRuntime @@ -17,8 +19,8 @@ import org.apache.commons.validator.UrlValidator import org.grails.plugins.codecs.HTMLCodec import org.springframework.context.MessageSource import org.springframework.context.i18n.LocaleContextHolder -import org.springframework.web.multipart.commons.CommonsMultipartFile import org.springframework.web.multipart.MultipartFile +import org.springframework.web.multipart.commons.CommonsMultipartFile import org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.StandardMultipartFile import spock.lang.Unroll import spock.util.mop.ConfineMetaClassChanges @@ -150,7 +152,7 @@ class FileUploaderServiceSpec extends BaseFileUploaderServiceSpecSetup { file.delete() where: - fileGroup | provider + fileGroup | provider 'testAmazon' | CDNProvider.AMAZON 'testGoogle' | CDNProvider.GOOGLE } @@ -160,8 +162,8 @@ class FileUploaderServiceSpec extends BaseFileUploaderServiceSpecSetup { File file = getFileInstance('/tmp/test.txt') 1 * googleCDNFileUploaderImplMock.uploadFile(_, _, _, _, _) >> { - String containerName, File fileToUpload, String fileName, boolean makePublic, long maxAge -> - throw new UploadFailureException(fileName, containerName, new Throwable()) + String containerName, File fileToUpload, String fileName, boolean makePublic, long maxAge -> + throw new UploadFailureException(fileName, containerName, new Throwable()) } mockGetProviderInstance('google') @@ -340,9 +342,12 @@ class FileUploaderServiceSpec extends BaseFileUploaderServiceSpecSetup { File fileInstance = getFileInstance('./temp/test.txt') and: 'Mocked method' - service.metaClass.saveFile = { String group, def file, String customFileName = '', - Object userInstance = null, Locale locale = null -> - return ufIleInstance + service.metaClass.saveFile = { + String group, + def file, + String customFileName = '', + Object userInstance = null, + Locale locale = null -> return ufIleInstance } when: 'cloneFile method is called and uFileInstance is missing' @@ -368,9 +373,12 @@ class FileUploaderServiceSpec extends BaseFileUploaderServiceSpecSetup { File fileInstance = getFileInstance('/tmp/test.txt') and: 'Mocked method' - service.metaClass.saveFile = { String group, def file, String customFileName = '', - Object userInstance = null, Locale locale = null -> - return ufIleInstance + service.metaClass.saveFile = { + String group, def file, + String customFileName = '', + Object userInstance = null, + Locale locale = null -> + return ufIleInstance } when: 'cloneFile method is called for valid parameters and UFile is not LOCAL type' @@ -672,9 +680,10 @@ class FileUploaderServiceSpec extends BaseFileUploaderServiceSpecSetup { UFileMoveHistory uFileMoveHistoryInstance = UFileMoveHistory.build(fromCDN: CDNProvider.RACKSPACE, toCDN: CDNProvider.GOOGLE, status: MoveStatus.FAILURE, ufile: UFile.build()) - service.metaClass.moveFilesToCDN = { List uFileList, CDNProvider toCDNProvider, - boolean makePublic = false -> - return + service.metaClass.moveFilesToCDN = { + List uFileList, + CDNProvider toCDNProvider, + boolean makePublic = false -> return } when: 'moveFailedFilesToCDN method is called and failedList containes no UFiles' @@ -717,7 +726,7 @@ class FileUploaderServiceSpec extends BaseFileUploaderServiceSpecSetup { service.ufileById(2, locale) then: 'Method throws FileNotFoundException' - FileNotFoundException e =thrown() + FileNotFoundException e = thrown() e.message == 'File not found' } @@ -857,4 +866,81 @@ class FileUploaderServiceSpec extends BaseFileUploaderServiceSpecSetup { then: 'It should renew the image path of all the Instance' uFileInstance.path != 'https://xyz/abc' } + + void "test saveFile method when file with same content uploaded twice"() { + given: 'A file instance' + File fileInstance = getFileInstance('/tmp/test.txt') + + and: 'Mocked authenticate method' + mockAuthenticateMethod() + + and: 'Mocked getFileNameAndExtensions' + mockGetFileNameAndExtensions() + + and: 'Mocked uploadFile method' + mockUploadFileMethod(true) + + and: 'Mocked file.Exists method' + mockExistMethod(true) + + and: 'Mocked getProviderInstance method' + service.metaClass.getProviderInstance = { String providerName -> + providerName == 'GOOGLE' ? googleCDNFileUploaderImplMock : amazonCDNFileUploaderImplMock + } + + and: 'Mocked FileGroup Instance' + new FileGroup(_) >> fileGroupMock + fileGroupMock.cdnProvider >> CDNProvider.GOOGLE + fileGroupMock.groupConfig >> [storageTypes: 'CDN', checksum: [calculate: true, algorithm: Algorithm.SHA1]] + + and: 'The saveFile method has been already called once for given file' + UFile savedUfileInstance = service.saveFile('testGoogle', fileInstance, 'test') + + when: 'saveFile method gets called again on the file with same content' + UFile.metaClass.static.findByChecksumAndChecksumAlgorithm = { String val, String val2 -> return new UFile() } + service.saveFile('testGoogle', fileInstance, 'test') + + then: 'DuplicateFileException must be thrown' + Exception exception = thrown(DuplicateFileException) + String message = "Checksum for file test.txt is ${savedUfileInstance.checksum} and that checksum refers to an" + + " existing file ${new UFile()} on server" + exception.message.equalsIgnoreCase(message) + } + + void "test saveFile method with invalid Algorithm instance"() { + given: 'A file instance' + File fileInstance = getFileInstance('/tmp/test.txt') + + and: 'Mocked authenticate method' + mockAuthenticateMethod() + + and: 'Mocked getFileNameAndExtensions' + mockGetFileNameAndExtensions() + + and: 'Mocked uploadFile method' + mockUploadFileMethod(true) + + and: 'Mocked file.Exists method' + mockExistMethod(true) + + and: 'Mocked getProviderInstance method' + service.metaClass.getProviderInstance = { String providerName -> + providerName == 'GOOGLE' ? googleCDNFileUploaderImplMock : amazonCDNFileUploaderImplMock + } + + and: 'Mocked FileGroup Instance' + new FileGroup(_) >> fileGroupMock + fileGroupMock.cdnProvider >> CDNProvider.GOOGLE + + and: 'Invalid algorithm instance supplied' + fileGroupMock.groupConfig >> [storageTypes: 'CDN', checksum: [calculate: true, algorithm: 'ABCD']] + + when: 'The saveFile method has been already called once for given file' + service.saveFile('testGoogle', fileInstance, 'test') + + then: 'IllegalArgumentException must be thrown' + Exception exception = thrown(IllegalArgumentException) + exception.message == "No enum constant ${Algorithm.canonicalName}.ABCD" + } } + diff --git a/src/test/groovy/com/causecode/fileuploader/UFileSpec.groovy b/src/test/groovy/com/causecode/fileuploader/UFileSpec.groovy index 2817917..ca05e91 100644 --- a/src/test/groovy/com/causecode/fileuploader/UFileSpec.groovy +++ b/src/test/groovy/com/causecode/fileuploader/UFileSpec.groovy @@ -24,7 +24,7 @@ class UFileSpec extends Specification implements BaseTestSetup { void "test isFileExists method for various cases"() { given: 'An instance of UFile' UFile ufileInstance = UFile.build() - ufileInstance.path = System.getProperty('user.dir') + '/temp' + ufileInstance.path = '/tmp' when: 'isFileExists method is called and file exists' boolean result = ufileInstance.isFileExists() diff --git a/src/test/groovy/com/causecode/fileuploader/util/checksum/ChecksumValidatorSpec.groovy b/src/test/groovy/com/causecode/fileuploader/util/checksum/ChecksumValidatorSpec.groovy new file mode 100644 index 0000000..8042de6 --- /dev/null +++ b/src/test/groovy/com/causecode/fileuploader/util/checksum/ChecksumValidatorSpec.groovy @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2011 - Present, CauseCode Technologies Pvt Ltd, India. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are not permitted. + */ +package com.causecode.fileuploader.util.checksum + +import com.causecode.fileuploader.BaseTestSetup +import com.causecode.fileuploader.FileGroup +import com.causecode.fileuploader.UFile +import com.causecode.fileuploader.util.checksum.exceptions.UnRecognizedFileTypeException +import grails.buildtestdata.mixin.Build +import org.grails.plugins.testing.GrailsMockMultipartFile +import spock.lang.Specification +import spock.lang.Unroll + +/** + * This class contains unit test cases for ChecksumValidator Class + * @author Milan Savaliya + * @since 3.1.0 + */ +@Build(UFile) +@SuppressWarnings(['JavaIoPackageAccess']) +class ChecksumValidatorSpec extends Specification implements BaseTestSetup { + + @Unroll + void "test Constructor with #fileName"() { + given: 'fileGroup instance with supplied fileName' + FileGroup fileGroupInstance = new FileGroup('testFile') + + and: 'supplied checksum config' + fileGroupInstance.groupConfig.checksum = checksum + + when: 'constructor is called' + ChecksumValidator instance = new ChecksumValidator(fileGroupInstance) + + then: 'expect a valid instance' + instance + + where: 'inputs are as below' + checksum << [null, [calculate: true, algorithm: Algorithm.SHA1]] + } + + void "test isCalculate method with valid fileGroup instance"() { + given: 'fileGroup instance' + FileGroup fileGroupInstance = new FileGroup('testLocal') + + and: 'mocked config object' + fileGroupInstance.groupConfig.checksum = [calculate: true, algorithm: Algorithm.SHA1] + + and: 'ChecksumValidator instance' + ChecksumValidator instance = new ChecksumValidator(fileGroupInstance) + + expect: 'shouldCalculateChecksum returns true' + instance.shouldCalculateChecksum() + } + + void "test getChecksum method with a file instance"() { + given: 'a file instance' + File fileInstance = new File('/tmp/testLocal.txt') + fileInstance.createNewFile() + fileInstance << 'Some dummy date in' + fileInstance.deleteOnExit() + + and: 'fileGroup instance' + String groupName = 'testLocal' + FileGroup fileGroupInstance = new FileGroup(groupName) + + and: 'valid ChecksumConfig object' + fileGroupInstance.groupConfig.checksum = [calculate: true, algorithm: Algorithm.SHA1] + + when: 'getChecksum method is called' + ChecksumValidator instance = new ChecksumValidator(fileGroupInstance) + String checksum = instance.getChecksum(fileInstance) + + then: 'expect that checksum is calculated' + checksum + } + + void "test getChecksum method with MultipartFile instance"() { + given: 'MultipartFile instance' + String fileName = 'testOne' + GrailsMockMultipartFile multipartFile = new GrailsMockMultipartFile(fileName, fileName, + 'text', [1, 2, 3, 4, 5] as byte[]) + + and: 'fileGroup instance' + String groupName = 'testLocal' + FileGroup fileGroupInstance = new FileGroup(groupName) + + and: 'valid ChecksumConfig object' + fileGroupInstance.groupConfig.checksum = [calculate: true, algorithm: Algorithm.SHA1] + + when: 'getChecksum method is called with fileGroupInstance' + ChecksumValidator instance = new ChecksumValidator(fileGroupInstance) + String checksum = instance.getChecksum(multipartFile) + + then: 'expect that checksum is calculated' + checksum + } + + void "test getAlgorithm method"() { + given: 'fileGroup instance' + String groupName = 'testLocal' + FileGroup fileGroupInstance = new FileGroup(groupName) + + and: 'valid ChecksumConfig object' + fileGroupInstance.groupConfig.checksum = [calculate: true, algorithm: Algorithm.SHA1] + + and: 'valid ChecksumValidator instance' + ChecksumValidator instance = new ChecksumValidator(fileGroupInstance) + + when: 'getAlgorithm method is called' + String algorithm = instance.algorithm + + then: 'expect supplied algorithm instance' + algorithm == Algorithm.SHA1.toString() + } + + void "test getFileInputBeanForFile method"() { + given: 'instance which is not a type of java.io.File or Spring\'s MultipartFileUpload class' + Object dummyObject = new Object() + + and: 'fileGroup instance' + FileGroup fileGroupInstance = new FileGroup('testLocal') + + and: 'valid checksumConfig object' + fileGroupInstance.groupConfig.checksum = [calculate: true, algorithm: Algorithm.SHA1] + + when: 'getChecksum method is called with dummyObject' + ChecksumValidator instance = new ChecksumValidator(fileGroupInstance) + instance.getChecksum(dummyObject) + + then: 'expect UnRecognizedFileTypeException' + Exception exception = thrown(UnRecognizedFileTypeException) + exception.message == "${dummyObject.class.name} is not recognized for FileInputBean" + } +} diff --git a/src/test/groovy/com/causecode/fileuploader/util/checksum/FileHashCalculatorSpec.groovy b/src/test/groovy/com/causecode/fileuploader/util/checksum/FileHashCalculatorSpec.groovy new file mode 100644 index 0000000..65642eb --- /dev/null +++ b/src/test/groovy/com/causecode/fileuploader/util/checksum/FileHashCalculatorSpec.groovy @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2011 - Present, CauseCode Technologies Pvt Ltd, India. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are not permitted. + */ +package com.causecode.fileuploader.util.checksum + +import com.causecode.fileuploader.util.checksum.beans.FileInputBean +import com.causecode.fileuploader.util.checksum.beans.SimpleFileInputBeanImpl +import spock.lang.Specification +import spock.lang.Unroll + +/** + * Unit Test Class for FileHashCalculator class + * @author Milan Savaliya + * @since 3.1.0 + */ +@SuppressWarnings(['JavaIoPackageAccess']) +class FileHashCalculatorSpec extends Specification { + + void "test for constructor with file parameter"() { + when: 'Invalid File Instance is given' + FileHashCalculator.newInstance(getFileInputBeanInstance(FileInstanceType.NULL)) + + then: 'FileNotFoundException must be thrown' + Exception exception = thrown(FileNotFoundException) + exception.message == 'File not found' + } + + @Unroll + void "test for constructor with file parameter and algorithm parameter"() { + expect: 'proper hashCalculator instance and algorithm instance' + hashCalculator && hashCalculator.algorithm == algorithm + + where: 'below inputs supplied' + hashCalculator | algorithm + + new FileHashCalculator(getFileInputBeanInstance(FileInstanceType.VALID)) | Algorithm.MD5 + new FileHashCalculator(getFileInputBeanInstance(FileInstanceType.VALID), Algorithm.SHA1) | Algorithm.SHA1 + } + + void "test calculateHash method #hashCalculator"() { + when: 'Hash is calculated' + String hash = hashCalculator.calculateHash() + + then: 'hash must not be null and empty' + hash + + where: 'below inputs supplied' + hashCalculator << [ + new FileHashCalculator(getFileInputBeanInstance(FileInstanceType.VALID)), + new FileHashCalculator(getFileInputBeanInstance(FileInstanceType.VALID), Algorithm.SHA1) + ] + } + + private static FileInputBean getFileInputBeanInstance(FileInstanceType fileInstanceType) { + if (fileInstanceType == FileInstanceType.NOT_EXISTS) { + return new SimpleFileInputBeanImpl(new File('')) + } + + if (fileInstanceType == FileInstanceType.VALID) { + File file = new File('/tmp/'.concat(System.currentTimeMillis() as String).concat('.txt')) + file.createNewFile() + file.deleteOnExit() + return new SimpleFileInputBeanImpl(file) + } + + return null + } +} + +enum FileInstanceType { + NULL, + NOT_EXISTS, + VALID, +} diff --git a/src/test/groovy/com/causecode/fileuploader/util/checksum/beans/MultipartFileInputBeanImplSpec.groovy b/src/test/groovy/com/causecode/fileuploader/util/checksum/beans/MultipartFileInputBeanImplSpec.groovy new file mode 100644 index 0000000..73218ed --- /dev/null +++ b/src/test/groovy/com/causecode/fileuploader/util/checksum/beans/MultipartFileInputBeanImplSpec.groovy @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2011 - Present, CauseCode Technologies Pvt Ltd, India. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are not permitted. + */ +package com.causecode.fileuploader.util.checksum.beans + +import org.grails.plugins.testing.GrailsMockMultipartFile +import org.springframework.web.multipart.MultipartFile +import spock.lang.Specification + +/** + * Unit Test Class for MultipartFileInputBeanImplSpec class + * @author Milan Savaliya + * @since 3.1.0 + */ + +class MultipartFileInputBeanImplSpec extends Specification { + + private static final String FILE_NAME = 'text.txt' + + void "test constructor with valid and invalid multipart instances"() { + when: 'given an null object as parameter' + MultipartFileInputBeanImpl.newInstance(null) + + then: 'expect a IllegalArgumentException' + Exception exception = thrown(IllegalArgumentException) + exception.message == 'Multipart Instance can not be null' + + when: 'given a proper instance as a parameter' + FileInputBean fileInputBean = MultipartFileInputBeanImpl.newInstance(Mock(MultipartFile)) + + then: 'expect that proper instance created' + fileInputBean + } + + void "test getName method"() { + given: 'mocked getName method of the fileInputBean' + GrailsMockMultipartFile mockMultipartFile = new GrailsMockMultipartFile(FILE_NAME) + FileInputBean fileInputBean = new MultipartFileInputBeanImpl(mockMultipartFile) + + expect: 'a Valid file name' + fileInputBean.name == FILE_NAME + } + + void "test getOriginalFilename method"() { + given: 'mocked getOriginalFilename method of the fileInputBean' + GrailsMockMultipartFile multipartFile = new GrailsMockMultipartFile(FILE_NAME, + FILE_NAME, + 'TEXT', [1, 2, 3] as byte[]) + FileInputBean fileInputBean = new MultipartFileInputBeanImpl(multipartFile) + + expect: 'Valid original FILE_NAME' + fileInputBean.originalFilename == FILE_NAME + } + + void "test getContentType method"() { + given: 'mocked getContentType method of the fileInputBean' + String contentType = 'TEXT' + GrailsMockMultipartFile multipartFile = new GrailsMockMultipartFile(FILE_NAME, '', contentType) + FileInputBean fileInputBean = new MultipartFileInputBeanImpl(multipartFile) + + expect: 'Valid content type' + fileInputBean.contentType == contentType + } + + void "test isEmpty method"() { + given: 'mocked isEmpty method of the fileInputBean' + GrailsMockMultipartFile multipartFile = new GrailsMockMultipartFile(FILE_NAME, [1, 2, 3] as byte[]) + FileInputBean fileInputBean = new MultipartFileInputBeanImpl(multipartFile) + + expect: 'file is not empty' + !fileInputBean.isEmpty() + } + + void "test getSize method"() { + given: 'mocked getSize method of the fileInputBean' + GrailsMockMultipartFile multipartFile = new GrailsMockMultipartFile(FILE_NAME, [1, 2, 3] as byte[]) + FileInputBean fileInputBean = new MultipartFileInputBeanImpl(multipartFile) + + expect: 'total number of supplied items in return' + fileInputBean.size == 3 + } + + void "test getBytes method"() { + given: 'mocked getBytes method of the fileInputBean' + GrailsMockMultipartFile multipartFile = new GrailsMockMultipartFile(FILE_NAME, [1, 2, 3] as byte[]) + FileInputBean fileInputBean = new MultipartFileInputBeanImpl(multipartFile) + + expect: 'total number of item is equal to total number of supplied array items' + fileInputBean.bytes.length == 3 + } + + void "test getInputStream method"() { + given: 'mocked getInputStream method of the fileInputBean' + GrailsMockMultipartFile multipartFile = new GrailsMockMultipartFile(FILE_NAME, [1, 2, 3] as byte[]) + FileInputBean fileInputBean = new MultipartFileInputBeanImpl(multipartFile) + + expect: 'that supplied inputStream instance is equal to returned one' + InputStream inputStream = fileInputBean.inputStream + inputStream.available() == 3 + inputStream.close() + } + + void "test isExists method"() { + given: 'mocked getInputStream method of the fileInputBean' + GrailsMockMultipartFile multipartFile = new GrailsMockMultipartFile(FILE_NAME) + FileInputBean fileInputBean = new MultipartFileInputBeanImpl(multipartFile) + + expect: 'that file exists on server space' + fileInputBean.isExists() + } +} diff --git a/src/test/groovy/com/causecode/fileuploader/util/checksum/beans/SimpleFileInputBeanImplSpec.groovy b/src/test/groovy/com/causecode/fileuploader/util/checksum/beans/SimpleFileInputBeanImplSpec.groovy new file mode 100644 index 0000000..4b1c29f --- /dev/null +++ b/src/test/groovy/com/causecode/fileuploader/util/checksum/beans/SimpleFileInputBeanImplSpec.groovy @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2011 - Present, CauseCode Technologies Pvt Ltd, India. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are not permitted. + */ +package com.causecode.fileuploader.util.checksum.beans + +import spock.lang.Specification +import spock.lang.Unroll + +/** + * Unit Test Class for SimpleFileInputBeanImpl class + * @author Milan Savaliya + * @since 3.1.0 + */ +@SuppressWarnings(['JavaIoPackageAccess']) +class SimpleFileInputBeanImplSpec extends Specification { + + private final String tempDirPath = '/tmp/' + private FileInputBean fileInputBean + private String fileName + + def setup() { + fileName = (System.currentTimeMillis() as String) + '.txt' + File file = new File(tempDirPath.concat(fileName)) + file.createNewFile() + file.deleteOnExit() + fileInputBean = new SimpleFileInputBeanImpl(file) + } + + private static File getFileInstance(String filename) { + File file = new File("/tmp/${filename}") + file.createNewFile() + file.deleteOnExit() + file + } + + @Unroll + void "test constructor with file object as:- #fileObject"() { + when: 'given an null object' + SimpleFileInputBeanImpl.newInstance(fileObject) + + then: 'expect a IllegalArgumentException' + Exception exception = thrown(exceptionToBeThrown) + exception.message == exceptionMessage + + where: 'the given values are as following' + fileObject | exceptionToBeThrown | exceptionMessage + null | IllegalArgumentException | 'File instance can not be null' + new File('text.txt') | FileNotFoundException | 'File with name text.txt not found' + } + + void "test getName method"() { + given: 'Proper file instance' + String filename = 'fooFile.txt' + File file = getFileInstance(filename) + + and: 'Valid FileInputBean instance' + FileInputBean fileInputBean = new SimpleFileInputBeanImpl(file) + + expect: 'that getName method returns a valid filename' + fileInputBean.name == filename + } + + void "test getOriginalFilename method"() { + expect: 'that getOriginalFilename method returns a valid filename' + fileInputBean.originalFilename + } + + @Unroll + void "test getContentType method wile file:- #simpleFileInputbean.filename"() { + expect: '' + simpleFileInputbean.contentType == output + + where: 'Given below inputs' + simpleFileInputbean | output + new SimpleFileInputBeanImpl(getFileInstance('causecode.txt')) | 'txt' + new SimpleFileInputBeanImpl(getFileInstance('causecode')) | null + } + + void "test isEmpty method"() { + expect: 'that isEmpty method returns true' + fileInputBean.isEmpty() + } + + void "test getSize method"() { + expect: 'that getSize method returns 0' + fileInputBean.size == 0 + } + + void "test getBytes method"() { + expect: 'that getBytes method returns 0' + fileInputBean.bytes.length == 0 + } + + void "test getInputStream method"() { + expect: 'that getInputStream method returns a valid inputStream object' + InputStream inputStream = fileInputBean.inputStream + inputStream.bytes.length == 0 + inputStream.close() + } + + void "test isExists method"() { + expect: 'True when File exists' + fileInputBean.isExists() + } +} diff --git a/src/test/groovy/com/causecode/fileuploader/util/checksum/exceptions/UnRecognizedFileTypeExceptionSpec.groovy b/src/test/groovy/com/causecode/fileuploader/util/checksum/exceptions/UnRecognizedFileTypeExceptionSpec.groovy new file mode 100644 index 0000000..a8a5125 --- /dev/null +++ b/src/test/groovy/com/causecode/fileuploader/util/checksum/exceptions/UnRecognizedFileTypeExceptionSpec.groovy @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2011 - Present, CauseCode Technologies Pvt Ltd, India. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are not permitted. + */ +package com.causecode.fileuploader.util.checksum.exceptions + +import spock.lang.Specification + +/** + * Unit test class for UnRecognizedFileTypeExceptionSpec class + * @author Milan Savaliya + * @since 3.1.0 + */ +class UnRecognizedFileTypeExceptionSpec extends Specification { + + void "test exception constructor"() { + given: 'an instance of UnRecognizedFileTypeException' + String message = 'Some Message' + Exception exception = new UnRecognizedFileTypeException(message) + + expect: 'valid UnRecognizedFileTypeException instance' + exception && exception.message == message + } + +}