Path downloadRemoteZip(Path targetPath) throws IOException, UserException {
        LOG.info("download plugin zip from: " + source);

        Path zip = Files.createTempFile(targetPath, ".plugin_", ".zip");
        cleanPathList.add(zip);

        // download zip
        try (InputStream in = getInputStreamFromUrl(source)) {
            Files.copy(in, zip, StandardCopyOption.REPLACE_EXISTING);
        }

        // .md5 check
        if (Strings.isNullOrEmpty(expectedChecksum)) {
            try (InputStream in = getInputStreamFromUrl(source + ".md5")) {
                BufferedReader br = new BufferedReader(new InputStreamReader(in));
                expectedChecksum = br.readLine();
            } catch (IOException e) {
                throw new UserException(e.getMessage() +
                        ". you should set md5sum in plugin properties or provide a md5 URI to check plugin file");
            }
        }

        DigestUtils.md5Hex(Files.readAllBytes(zip));
        final String actualChecksum = DigestUtils.md5Hex(Files.readAllBytes(zip));

        if (!StringUtils.equalsIgnoreCase(expectedChecksum, actualChecksum)) {
            throw new UserException(
                    "MD5 check mismatch, expected " + expectedChecksum + " but actual " + actualChecksum);
        }

        return zip;
    }
@Test
    public void testDownloadAndValidateZipIOException() {
        PluginZip util = new PluginZip("http://io-exception", null);
        try {
            Path zipPath = util.downloadRemoteZip(PluginTestUtil.getTestPath("target"));
        } catch (Exception e) {
            assertTrue(e instanceof IOException);
        }
    }