Skip to content

Commit

Permalink
Add tests and update some error handlings
Browse files Browse the repository at this point in the history
  • Loading branch information
roemerm committed Jan 16, 2025
1 parent 2f49eca commit 9c85a0e
Show file tree
Hide file tree
Showing 5 changed files with 243 additions and 7 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@
/package-lock.json
/yarn-error.log
/test/asset/adf_bytes_scan_height_fixed.jpg
/test/asset/sample.pdf
/test/asset/sample1.jpg
/test/asset/sample2.jpg
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"codecov": "^3.8.3",
"eslint": "^9.1.0",
"mocha": "^11.0.1",
"nock": "^13.5.6",
"nodemon": "^3.0.1",
"nyc": "^17.0.0",
"prettier": "^3.0.0",
Expand Down
29 changes: 22 additions & 7 deletions src/nextcloud/nextcloud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,32 @@ async function uploadToNextcloud(
const folderUrl = buildFolderUrl(baseUrl, username, uploadFolder);
const uploadUrl = buildUrl(folderUrl, [fileName]);

const fileBuffer: Buffer = await fs.readFile(filePath);
let fileBuffer: Buffer;
try {
fileBuffer = await fs.readFile(filePath);
} catch (e) {
console.error("Fail to read file:", e);
return;
}
const auth = { username, password };

console.log(`Start uploading to Nextcloud: ${fileName}`);
try {
await axios({
const response = await axios({
method: "PUT",
url: uploadUrl,
auth,
data: fileBuffer,
});

let action: string;
if (response.status === 201) {
action = "created";
} else {
action = "updated";
}
console.log(
`Document successfully uploaded to Nextcloud. (Folder: ${uploadFolder}, File: ${fileName})`,
`Document successfully ${action} file at Nextcloud. (Folder: ${uploadFolder}, File: ${fileName})`,
);
} catch (error) {
console.error("Fail to upload document:", error);
Expand All @@ -67,7 +79,7 @@ async function checkFolderAndUpload(
) {
const folderExists = await checkNextcloudFolderExists(nextcloudConfig);
if (!folderExists) {
console.error("Upload folder does not exist; skipping upload");
console.info("Upload folder does not exist or user has no permission; skipping upload");
return;
}

Expand All @@ -94,10 +106,13 @@ async function checkNextcloudFolderExists(
} catch (error) {
const axiosError = error as AxiosError;
if (axiosError.response?.status === 404) {
console.error(`Upload folder '${uploadFolder}' not found in Nextcloud`);
return false;
console.warn(`Upload folder '${uploadFolder}' not found in Nextcloud`);
}else if (axiosError.response?.status === 401) {
console.warn(`User has no permission to access upload folder '${uploadFolder}' in Nextcloud`);
} else {
console.error("Fail to check upload folder exists:", axiosError.toJSON());
}
throw error;
return false;
}
return true;
}
Expand Down
198 changes: 198 additions & 0 deletions test/nextcloud.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
import { describe } from "mocha";
import path from "path";
import {
ScanContent,
ScanPage
} from "../src/ScanContent";
import {
uploadImagesToNextcloud,
uploadPdfToNextcloud
} from "../src/nextcloud/nextcloud";
import { NextcloudConfig } from "../src/nextcloud/NextcloudConfig";
import { convertToPdf } from "../src/pdfProcessing";
import fs from "fs";
import nock from "nock";

describe("nextcloud", () => {
// prepare test data
const fileName = "sample.jpg";
const filePath = path.resolve(__dirname, "./asset/" + fileName);
const nextcloudUrl = "https://nextcloud.example.test";
const username = "scanner";
const password = "pa$$word";
const uploadFolder = "scan";

let scanJobContent: ScanContent;
let scanPage: ScanPage;
let nextcloudConfig: NextcloudConfig;

beforeEach(async () => {
nock.disableNetConnect()
nock.enableNetConnect(nextcloudUrl)

nock(nextcloudUrl, { allowUnmocked: true })
.get(/.*/)
.reply(503, 'Service Unavailable');

scanJobContent = { elements: [] };
scanPage = {
pageNumber: 1,
path: filePath,
width: 400,
height: 300,
xResolution: 96,
yResolution: 96,
};

nextcloudConfig = {
baseUrl: nextcloudUrl,
username: username,
password: password,
uploadFolder: uploadFolder,
keepFiles: false,
}
});

describe("upload images to Nextcloud", () => {
it('success upload single image', async () => {
nock(nextcloudUrl)
.intercept(`/remote.php/dav/files/${username}/${uploadFolder}`, 'PROPFIND')
.basicAuth({user: username, pass: password})
.reply(207, "<?xml version=\"1.0\"?>\n" +
"<d:multistatus xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\" xmlns:oc=\"http://owncloud.org/ns\" xmlns:nc=\"http://nextcloud.org/ns\"><d:response><d:propstat><d:status>HTTP/1.1 200 OK</d:status></d:propstat></d:response></d:multistatus>")
.intercept(`/remote.php/dav/files/${username}/${uploadFolder}/${fileName}`, 'PUT')
.basicAuth({user: username, pass: password})
.reply(201);

scanJobContent.elements.push(scanPage);

await uploadImagesToNextcloud(scanJobContent, nextcloudConfig);
});

it('success upload multiple images', async () => {
const fileName1 = "sample1.jpg";
const fileName2 = "sample2.jpg";
const filePath1 = path.resolve(__dirname, "./asset/" + fileName1);
const filePath2 = path.resolve(__dirname, "./asset/" + fileName2);

nock(nextcloudUrl)
.intercept(`/remote.php/dav/files/${username}/${uploadFolder}`, 'PROPFIND')
.basicAuth({user: username, pass: password})
.reply(207, "<?xml version=\"1.0\"?>\n" +
"<d:multistatus xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\" xmlns:oc=\"http://owncloud.org/ns\" xmlns:nc=\"http://nextcloud.org/ns\"><d:response><d:propstat><d:status>HTTP/1.1 200 OK</d:status></d:propstat></d:response></d:multistatus>")
.intercept(`/remote.php/dav/files/${username}/${uploadFolder}/${fileName}`, 'PUT')
.basicAuth({user: username, pass: password})
.reply(201)
.intercept(`/remote.php/dav/files/${username}/${uploadFolder}/${fileName1}`, 'PUT')
.basicAuth({user: username, pass: password})
.reply(201)
.intercept(`/remote.php/dav/files/${username}/${uploadFolder}/${fileName2}`, 'PUT')
.basicAuth({user: username, pass: password})
.reply(201);

fs.copyFileSync(filePath, filePath1);
fs.copyFileSync(filePath, filePath2);

scanJobContent.elements.push(scanPage);

const scanPage2 = {...scanPage};
scanPage2.pageNumber = 2;
scanPage2.path = filePath1;
scanJobContent.elements.push(scanPage2);

const scanPage3 = {...scanPage};
scanPage3.pageNumber = 3;
scanPage3.path = filePath2;
scanJobContent.elements.push(scanPage3);

await uploadImagesToNextcloud(scanJobContent, nextcloudConfig);
});

it('user not authorized', async () => {
nock(nextcloudUrl)
.intercept(`/remote.php/dav/files/${username}/${uploadFolder}`, 'PROPFIND')
.basicAuth({user: username, pass: password})
.reply(401, "Unauthorized");
scanJobContent = { elements: [] };

await uploadImagesToNextcloud(scanJobContent, nextcloudConfig);
});

it('upload path does not exist', async () => {
nock(nextcloudUrl)
.intercept(`/remote.php/dav/files/${username}/${uploadFolder}`, 'PROPFIND')
.basicAuth({user: username, pass: password})
.reply(404, "No such file or directory");
scanJobContent = { elements: [] };

await uploadImagesToNextcloud(scanJobContent, nextcloudConfig);
});

it('upload file failed', async () => {
nock(nextcloudUrl)
.intercept(`/remote.php/dav/files/${username}/${uploadFolder}`, 'PROPFIND')
.basicAuth({user: username, pass: password})
.reply(207, "<?xml version=\"1.0\"?>\n" +
"<d:multistatus xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\" xmlns:oc=\"http://owncloud.org/ns\" xmlns:nc=\"http://nextcloud.org/ns\"><d:response><d:propstat><d:status>HTTP/1.1 200 OK</d:status></d:propstat></d:response></d:multistatus>")
.intercept(`/remote.php/dav/files/${username}/${uploadFolder}/${fileName}`, 'PUT')
.basicAuth({user: username, pass: password})
.reply(404);

scanJobContent.elements.push(scanPage);

await uploadImagesToNextcloud(scanJobContent, nextcloudConfig);
});

it('upload file not found', async () => {
const unknownFilePath = path.resolve(__dirname, "./asset/unknown.jpg");
nock(nextcloudUrl)
.intercept(`/remote.php/dav/files/${username}/${uploadFolder}`, 'PROPFIND')
.basicAuth({user: username, pass: password})
.reply(207, "<?xml version=\"1.0\"?>\n" +
"<d:multistatus xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\" xmlns:oc=\"http://owncloud.org/ns\" xmlns:nc=\"http://nextcloud.org/ns\"><d:response><d:propstat><d:status>HTTP/1.1 200 OK</d:status></d:propstat></d:response></d:multistatus>")
.intercept(`/remote.php/dav/files/${username}/${uploadFolder}/${fileName}`, 'PUT')
.basicAuth({user: username, pass: password})
.reply(201);

const scanPage = {
pageNumber: 1,
path: unknownFilePath,
width: 400,
height: 300,
xResolution: 96,
yResolution: 96,
};
scanJobContent.elements.push(scanPage);

await uploadImagesToNextcloud(scanJobContent, nextcloudConfig);
});
});

describe("uploadPdfToNextcloud", () => {
it('success upload pdf document', async () => {
const pdfFilePath = await convertToPdf(scanPage, false);
const pdfFileName = "sample.pdf";

nock(nextcloudUrl)
.intercept(`/remote.php/dav/files/${username}/${uploadFolder}`, 'PROPFIND')
.basicAuth({user: username, pass: password})
.reply(207, "<?xml version=\"1.0\"?>\n" +
"<d:multistatus xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\" xmlns:oc=\"http://owncloud.org/ns\" xmlns:nc=\"http://nextcloud.org/ns\"><d:response><d:propstat><d:status>HTTP/1.1 200 OK</d:status></d:propstat></d:response></d:multistatus>")
.intercept(`/remote.php/dav/files/${username}/${uploadFolder}/${pdfFileName}`, 'PUT')
.basicAuth({user: username, pass: password})
.reply(201);

await uploadPdfToNextcloud(pdfFilePath, nextcloudConfig);
});

it('pdf document not set', async () => {
nock(nextcloudUrl)
.intercept(`/remote.php/dav/files/${username}/${uploadFolder}`, 'PROPFIND')
.basicAuth({user: username, pass: password})
.reply(207, "<?xml version=\"1.0\"?>\n" +
"<d:multistatus xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\" xmlns:oc=\"http://owncloud.org/ns\" xmlns:nc=\"http://nextcloud.org/ns\"><d:response><d:propstat><d:status>HTTP/1.1 200 OK</d:status></d:propstat></d:response></d:multistatus>");

await uploadPdfToNextcloud(null, nextcloudConfig);
});
});
});
19 changes: 19 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1580,6 +1580,11 @@ json-stable-stringify-without-jsonify@^1.0.1:
resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz"
integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==

json-stringify-safe@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==

json5@^2.2.3:
version "2.2.3"
resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz"
Expand Down Expand Up @@ -1772,6 +1777,15 @@ natural-compare@^1.4.0:
resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz"
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==

nock@^13.5.6:
version "13.5.6"
resolved "https://registry.yarnpkg.com/nock/-/nock-13.5.6.tgz#5e693ec2300bbf603b61dae6df0225673e6c4997"
integrity sha512-o2zOYiCpzRqSzPj0Zt/dQ/DqZeYoaQ7TUonc/xUPjCGl9WeHpNbxgVvOquXYAaJzI0M9BXV3HTzG0p8IUAbBTQ==
dependencies:
debug "^4.1.0"
json-stringify-safe "^5.0.1"
propagate "^2.0.0"

node-fetch@^2.6.1:
version "2.6.9"
resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz"
Expand Down Expand Up @@ -2000,6 +2014,11 @@ process-on-spawn@^1.0.0:
dependencies:
fromentries "^1.2.0"

propagate@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/propagate/-/propagate-2.0.1.tgz#40cdedab18085c792334e64f0ac17256d38f9a45"
integrity sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==

proxy-from-env@^1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz"
Expand Down

0 comments on commit 9c85a0e

Please sign in to comment.