diff --git a/README.md b/README.md
index 79fd3a4..75b63cb 100644
--- a/README.md
+++ b/README.md
@@ -164,7 +164,7 @@ Create a SAP Document Management Integration Option [Service instance and key](h
6. The `Attachments` type has generated an out-of-the-box Attachments table (see highlighted box) at the bottom of the Object page:
-7. **Upload a file** by going into Edit mode and either using the **Upload** button on the Attachments table or by drag/drop. Then click the **Save** button to have that file stored in SAP Document Management Integration Option. We demonstrate this by uploading the PDF file from [_xmpl/db/content/Solar Panel Report.pdf_](./xmpl/db/content/Solar%20Panel%20Report.pdf):
+7. **Upload a file** by going into Edit mode and either using the **Upload** button on the Attachments table or by drag/drop. The file is then stored in SAP Document Management Integration Option. We demonstrate this by uploading the PDF file from [_xmpl/db/content/Solar Panel Report.pdf_](./xmpl/db/content/Solar%20Panel%20Report.pdf):
8. **Open a file** by clicking on the attachment. We demonstrate this by opening the previously uploaded PDF file: `Solar Panel Report.pdf`
diff --git a/lib/handler/index.js b/lib/handler/index.js
index f9438f9..7e0c11a 100644
--- a/lib/handler/index.js
+++ b/lib/handler/index.js
@@ -1,6 +1,7 @@
const { getConfigurations } = require("../util");
const axios = require("axios").default;
const FormData = require("form-data");
+const { errorMessage } = require("../util/messageConsts");
async function readAttachment(Key, token, credentials) {
const document = await readDocument(Key, token, credentials.uri);
@@ -70,7 +71,7 @@ async function getFolderIdByPath(req, credentials, token, attachments) {
const response = await axios.get(getFolderByPathURL, config);
return response.data.properties["cmis:objectId"].value;
} catch (error) {
- let statusText = "An Error Occurred";
+ let statusText = errorMessage;
if (error.response?.statusText) {
statusText = error.response.statusText;
}
@@ -80,14 +81,13 @@ async function getFolderIdByPath(req, credentials, token, attachments) {
}
async function createFolder(req, credentials, token, attachments) {
- const up_ = attachments.keys.up_.keys[0].$generatedFieldName;
- const idValue = up_.split("__")[1];
+ const upID = attachments.keys.up_.keys[0].$generatedFieldName;
const { repositoryId } = getConfigurations();
const folderCreateURL = credentials.uri + "browser/" + repositoryId + "/root";
const formData = new FormData();
formData.append("cmisaction", "createFolder");
formData.append("propertyId[0]", "cmis:name");
- formData.append("propertyValue[0]", req.data[idValue]);
+ formData.append("propertyValue[0]", req.data[upID]);
formData.append("propertyId[1]", "cmis:objectTypeId");
formData.append("propertyValue[1]", "cmis:folder");
formData.append("succinct", "true");
@@ -104,7 +104,6 @@ async function createAttachment(
data,
credentials,
token,
- attachments,
parentId
) {
const { repositoryId } = getConfigurations();
@@ -171,6 +170,32 @@ async function deleteFolderWithAttachments(credentials, token, parentId) {
return response;
}
+async function getAttachment(uri, token, objectId) {
+ const { repositoryId } = getConfigurations();
+ const getAttachmentURL =
+ uri
+ + "browser/"
+ + repositoryId
+ + "/root?"
+ + "cmisselector=object&objectId="
+ + objectId
+ + "&succinct=true";
+
+ const config = {
+ headers: { Authorization: `Bearer ${token}` },
+ };
+ try {
+ return await axios.get(getAttachmentURL, config);
+ } catch (error) {
+ let statusText = errorMessage;
+ if (error.response?.statusText) {
+ statusText = error.response.statusText;
+ }
+ console.log(statusText);
+ return null;
+ }
+}
+
async function renameAttachment(
modifiedAttachment,
credentials,
@@ -215,6 +240,7 @@ module.exports = {
createAttachment,
deleteAttachmentsOfFolder,
deleteFolderWithAttachments,
+ getAttachment,
readAttachment,
renameAttachment
};
diff --git a/lib/persistence/index.js b/lib/persistence/index.js
index c78d8cd..d3b99a9 100644
--- a/lib/persistence/index.js
+++ b/lib/persistence/index.js
@@ -17,11 +17,21 @@ async function getDraftAttachments(attachments, req, repositoryId) {
.where(conditions)
}
+async function getDraftAttachmentsForUpID(attachments, req, repositoryId) {
+ const up_ = attachments.keys.up_.keys[0].$generatedFieldName;
+ const conditions = {
+ [up_]: req.data[up_],
+ repositoryId: repositoryId
+ };
+ return await SELECT("filename", "mimeType", "content", "url", "ID", "HasActiveEntity")
+ .from(attachments)
+ .where(conditions)
+}
+
async function getFolderIdForEntity(attachments, req, repositoryId) {
const up_ = attachments.keys.up_.keys[0].$generatedFieldName;
- const idValue = up_.split("__")[1];
const conditions = {
- [up_]: req.data[idValue],
+ [up_]: req.data[up_],
repositoryId: repositoryId
};
return await SELECT.from(attachments)
@@ -29,6 +39,14 @@ async function getFolderIdForEntity(attachments, req, repositoryId) {
.where(conditions);
}
+async function updateAttachmentInDraft(req, data) {
+ const up_ = req.target.keys.up_.keys[0].$generatedFieldName;
+ const idValue = up_.split("__")[1];
+ return await UPDATE(req.target)
+ .set({ folderId: data.folderId, url: data.url, status: "Clean" })
+ .where({ [idValue]: req.data[idValue] });
+}
+
async function getURLsToDeleteFromAttachments(deletedAttachments, attachments) {
return await SELECT.from(attachments)
.columns("url")
@@ -36,35 +54,37 @@ async function getURLsToDeleteFromAttachments(deletedAttachments, attachments) {
}
async function getExistingAttachments(attachmentIDs, attachments) {
- return await SELECT("filename", "url", "ID","folderId")
+ return await SELECT("filename", "url", "ID", "folderId")
.from(attachments)
.where({ ID: { in: [...attachmentIDs] }});
}
async function setRepositoryId(attachments, repositoryId) {
- if(attachments){
+ if(attachments) {
let nullAttachments = await SELECT()
.from(attachments)
.where({ repositoryId: null });
- if (!nullAttachments || nullAttachments.length === 0) {
- return;
- }
+ if (!nullAttachments || nullAttachments.length === 0) {
+ return;
+ }
- for (let attachment of nullAttachments) {
- await UPDATE(attachments)
- .set({ repositoryId: repositoryId })
- .where({ ID: attachment.ID });
- }
+ for (let attachment of nullAttachments) {
+ await UPDATE(attachments)
+ .set({ repositoryId: repositoryId })
+ .where({ ID: attachment.ID });
+ }
}
}
module.exports = {
getDraftAttachments,
+ getDraftAttachmentsForUpID,
getURLsToDeleteFromAttachments,
getURLFromAttachments,
getFolderIdForEntity,
+ updateAttachmentInDraft,
getExistingAttachments,
setRepositoryId
};
diff --git a/lib/sdm.js b/lib/sdm.js
index 70b14c5..b20b7f9 100644
--- a/lib/sdm.js
+++ b/lib/sdm.js
@@ -8,6 +8,7 @@ const {
createAttachment,
deleteAttachmentsOfFolder,
deleteFolderWithAttachments,
+ getAttachment,
readAttachment,
renameAttachment
} = require("./handler/index");
@@ -20,12 +21,14 @@ const {
} = require("./util/index");
const {
getDraftAttachments,
+ getDraftAttachmentsForUpID,
getURLsToDeleteFromAttachments,
getURLFromAttachments,
getFolderIdForEntity,
+ updateAttachmentInDraft,
setRepositoryId
} = require("../lib/persistence");
-const { duplicateDraftFileErr, emptyFileErr, renameFileErr, virusFileErr, duplicateFileErr, versionedRepositoryErr, otherFileErr } = require("./util/messageConsts");
+const { duplicateDraftFileErr, renameFileErr, virusFileErr, duplicateFileErr, versionedRepositoryErr, otherFileErr, userDoesNotHaveScopeMessage, userNotAuthorisedError } = require("./util/messageConsts");
module.exports = class SDMAttachmentsService extends (
require("@cap-js/attachments/lib/basic")
@@ -49,7 +52,7 @@ module.exports = class SDMAttachmentsService extends (
const repoInfo = await getRepositoryInfo(this.creds, token);
isVersioned = await isRepositoryVersioned(repoInfo, repositoryId);
} else {
- isVersioned = repotype == "versioned" ? true : false;
+ isVersioned = repotype == "versioned";
}
if (isVersioned) {
req.reject(400, versionedRepositoryErr);
@@ -57,7 +60,6 @@ module.exports = class SDMAttachmentsService extends (
}
async get(attachments, keys, req) {
- await this.checkRepositoryType(req);
const response = await getURLFromAttachments(keys, attachments);
const token = await fetchAccessToken(
this.creds,
@@ -68,38 +70,82 @@ module.exports = class SDMAttachmentsService extends (
return content;
}
- async draftSaveHandler(req) {
+ async renameHandler(req) {
const { repositoryId } = getConfigurations();
await this.checkRepositoryType(req);
const attachments = cds.model.definitions[req.query.target.name + ".attachments"];
const attachment_val = await getDraftAttachments(attachments, req, repositoryId);
if (attachment_val.length > 0) {
- await this.isFileNameDuplicateInDrafts(attachment_val,req);
+ await this.isFileNameDuplicateInDrafts(attachment_val, req);
const token = await fetchAccessToken(
this.creds,
req.user.tokenInfo.getTokenValue()
);
- const attachment_val_rename = attachment_val.filter(attachment => attachment.HasActiveEntity === true);
- const attachment_val_create = attachment_val.filter(attachment => attachment.HasActiveEntity === false);
+ let attachment_val_rename = [];
+ let draft_attachments = [];
+ draft_attachments = attachment_val.filter(attachment => attachment.HasActiveEntity === false);
+ attachment_val_rename = attachment_val.filter(attachment => attachment.HasActiveEntity === true);
+
const attachmentIDs = attachment_val_rename.map(attachment => attachment.ID);
let modifiedAttachments = [];
modifiedAttachments = await checkAttachmentsToRename(attachment_val_rename, attachmentIDs, attachments)
+
+ draft_attachments.forEach( attachment => {
+ const filenameInDraft = attachment.filename;
+ const objectId = attachment.url;
+ const attachmentData = this.getAttachementDataInSDM(this.creds.uri, token, objectId);
+ const filenameInSDM = attachmentData.filename;
+ if(filenameInDraft !== filenameInSDM){
+ modifiedAttachments.push({ID:attachment.ID, url: attachment.url, name: filenameInDraft, prevname: filenameInSDM, folderId:attachmentData.folderId});
+ }
+ });
+
let errorMessage = ""
if(modifiedAttachments.length>0){
errorMessage = await this.rename(modifiedAttachments, token, req)
}
- if(attachment_val_create.length>0){
- errorMessage = await this.create(attachment_val_create, attachments, req, token, errorMessage)
- }
+
if(errorMessage.length != 0){
req.warn(500, errorMessage);
}
}
}
+ async getAttachementDataInSDM(uri, token, objectId) {
+ const response = await getAttachment(uri, token, objectId);
+ const responseData = { filename: response?.data?.succinctProperties["cmis:name"], folderId: response?.data?.succinctProperties["sap:parentIds"][0] };
+ return responseData;
+ }
+
+ async draftSaveHandler(req) {
+ if (req?.data?.content) {
+ const { repositoryId } = getConfigurations();
+ await this.checkRepositoryType(req);
+ const draftAttachments = req.target;
+ const attachment_val = await getDraftAttachmentsForUpID(draftAttachments, req, repositoryId);
+
+ if (attachment_val.length > 0) {
+ await this.isFileNameDuplicateInDrafts(attachment_val,req);
+ const token = await fetchAccessToken(
+ this.creds,
+ req.user.tokenInfo.getTokenValue()
+ );
+ let attachment_val_create = [];
+ if (req.data.content) {
+ attachment_val_create = attachment_val.filter(attachment => attachment.HasActiveEntity === false && attachment.ID === req.data.ID);
+ }
+ if(attachment_val_create.length>0){
+ attachment_val_create[0].content = req.data.content;
+ await this.create(attachment_val_create, draftAttachments, req, token)
+ }
+ }
+ req.data.content = null;
+ }
+ }
+
async rename(modifiedAttachments, token, req){
await this.checkRepositoryType(req);
const failedReq = await this.onRename(
@@ -115,55 +161,28 @@ module.exports = class SDMAttachmentsService extends (
return errorResponse;
}
- async create(attachment_val_create, attachments, req, token, renameError){
- let parentId = await this.getParentId(attachments,req,token)
- const failedReq = await this.onCreate(
+ async create(attachment_val_create, attachments, req, token){
+ let parentId = await this.getParentId(attachments, req, token)
+ await this.onCreate(
attachment_val_create,
this.creds,
token,
- attachments,
req,
parentId
);
- let errorResponse;
- if (renameError){
- errorResponse = renameError;
- }
- else{
- errorResponse = ""
- }
- let virusFiles = [];
- let duplicateFiles = [];
- let otherFiles = [];
- failedReq.forEach((attachment) => {
- if (attachment.typeOfError == 'virus') {
- virusFiles.push(attachment.name)
- }
- else if (attachment.typeOfError == 'duplicate') {
- duplicateFiles.push(attachment.name)
- }
- else{
- otherFiles.push(attachment.message);
- }
- });
- if(virusFiles.length != 0){
- errorResponse = errorResponse + virusFileErr(virusFiles)
- }
- if(duplicateFiles.length != 0){
- errorResponse = errorResponse + duplicateFileErr(duplicateFiles);
- }
- if(otherFiles.length != 0){
- errorResponse = errorResponse + otherFileErr(otherFiles);
- }
- return errorResponse;
}
-
async getParentId(attachments,req,token){
const { repositoryId } = getConfigurations();
const folderIds = await getFolderIdForEntity(attachments, req, repositoryId);
- let parentId = "";
- if (folderIds?.length == 0) {
+ let parentId = null;
+ for (const folder of folderIds) {
+ if (folder.folderId !== null) {
+ parentId = folder.folderId;
+ break;
+ }
+ }
+ if (!parentId) {
const folderId = await getFolderIdByPath(
req,
this.creds,
@@ -179,15 +198,16 @@ module.exports = class SDMAttachmentsService extends (
token,
attachments
);
+ if (response.status == 403 && response.response.data == userDoesNotHaveScopeMessage) {
+ req.reject(403, userNotAuthorisedError);
+ }
parentId = response.data.succinctProperties["cmis:objectId"];
}
- } else {
- parentId = folderIds ? folderIds[0].folderId : "";
}
return parentId;
}
- async isFileNameDuplicateInDrafts(data,req) {
+ async isFileNameDuplicateInDrafts(data, req) {
let fileNames = [];
for (let index in data) {
fileNames.push(data[index].filename);
@@ -318,63 +338,37 @@ module.exports = class SDMAttachmentsService extends (
}
}
- async onCreate(data, credentials, token, attachments, req, parentId) {
- let failedReq = [],
- Ids = [],
- success = [],
- success_ids = [];
+ async onCreate(data, credentials, token, req, parentId) {
+ let fileNames = [];
const { repositoryId } = getConfigurations();
await Promise.all(
data.map(async (d) => {
- // Check if d.content is null
- if (d.content === null) {
- failedReq.push(emptyFileErr(d.filename));
- Ids.push(d.ID);
+ const response = await createAttachment(
+ d,
+ credentials,
+ token,
+ parentId
+ );
+ if (response.status == 201) {
+ d.folderId = parentId;
+ d.url = response.data?.succinctProperties["cmis:objectId"];
+ d.repositoryId = repositoryId;
+ d.content = null;
+ await updateAttachmentInDraft(req, d);
} else {
- const response = await createAttachment(
- d,
- credentials,
- token,
- attachments,
- parentId
- );
- if (response.status == 201) {
- d.folderId = parentId;
- d.url = response.data.succinctProperties["cmis:objectId"];
- d.repositoryId = repositoryId;
- d.content = null;
- success_ids.push(d.ID);
- success.push(d);
- } else {
- Ids.push(d.ID);
- if(response.response.data.message == 'Malware Service Exception: Virus found in the file!'){
- failedReq.push({typeOfError:'virus',name:d.filename})
- }
- else if(response.response.data.exception == "nameConstraintViolation"){
- failedReq.push({typeOfError:'duplicate',name:d.filename});
- }
- else{
- failedReq.push({typeOfError:'other',message:response.response.data.message});
- }
+ fileNames.push(d.filename);
+ if(response.response.data.message == 'Malware Service Exception: Virus found in the file!'){
+ req.reject(virusFileErr(fileNames));
+ }
+ else if(response.response.data.exception == "nameConstraintViolation"){
+ req.reject(duplicateFileErr(fileNames));
+ }
+ else{
+ req.reject(otherFileErr(fileNames));
}
}
})
);
-
- let removeCondition = (obj) => Ids.includes(obj.ID);
- req.data.attachments = req.data.attachments.filter(
- (obj) => !removeCondition(obj)
- );
- let removeSuccessAttachments = (obj) => success_ids.includes(obj.ID);
-
- // Filter out successful attachments
- req.data.attachments = req.data.attachments.filter(
- (obj) => !removeSuccessAttachments(obj)
- );
-
- // Add successful attachments to the end of the attachments array
- req.data.attachments = [...req.data.attachments, ...success];
- return failedReq;
}
async onRename(modifiedAttachments, credentials, token, req) {
@@ -438,9 +432,16 @@ module.exports = class SDMAttachmentsService extends (
entity,
this.attachDeletionData.bind(this)
);
- srv.before("READ", [target,target.drafts], this.setRepository.bind(this))
- srv.before("READ", [target,target.drafts], this.filterAttachments.bind(this))
- srv.before("SAVE", entity, this.draftSaveHandler.bind(this));
+ srv.before("READ", [target, target.drafts], this.setRepository.bind(this))
+ srv.before("READ", [target, target.drafts], this.filterAttachments.bind(this))
+ srv.before("SAVE", entity, this.renameHandler.bind(this));
+ if (target.drafts) {
+ srv.before(
+ "PUT",
+ target.drafts,
+ this.draftSaveHandler.bind(this)
+ );
+ }
srv.after(
["DELETE", "UPDATE"],
entity,
diff --git a/lib/util/messageConsts.js b/lib/util/messageConsts.js
index aed7367..bfa2e10 100644
--- a/lib/util/messageConsts.js
+++ b/lib/util/messageConsts.js
@@ -1,7 +1,5 @@
module.exports.duplicateDraftFileErr = (duplicateDraftFiles) =>
`The file(s) ${duplicateDraftFiles} have been added multiple times. Please rename and try again.`;
-module.exports.emptyFileErr = (fileName) =>
- `Content of file ${fileName} is empty. Either it is corrupted or not uploaded properly.`;
module.exports.virusFileErr = (virusFiles) => {
const bulletPoints = virusFiles.map(file => `• ${file}`).join('\n');
return `The following files contain potential malware and cannot be uploaded:\n${bulletPoints}\n`;
@@ -19,3 +17,6 @@ module.exports.otherFileErr = (otherFiles) => {
return `${message}\n`;
};
module.exports.versionedRepositoryErr = 'Attachments are not supported for a versioned repository.';
+module.exports.userNotAuthorisedError = 'You do not have the required permissions to upload attachments. Please contact your administrator for access.';
+module.exports.userDoesNotHaveScope = 'User does not have required scope';
+module.exports.errorMessage = 'An error occurred';
diff --git a/test/lib/handler/index.test.js b/test/lib/handler/index.test.js
index 127b149..c509857 100644
--- a/test/lib/handler/index.test.js
+++ b/test/lib/handler/index.test.js
@@ -26,9 +26,11 @@ const {
getFolderIdByPath,
createFolder,
deleteFolderWithAttachments,
+ getAttachment,
renameAttachment,
getRepositoryInfo
} = require("../../../lib/handler/index");
+const { errorMessage } = require("../../../lib/util/messageConsts");
describe("handlers", () => {
describe("ReadAttachment function", () => {
@@ -381,7 +383,55 @@ describe("handlers", () => {
});
});
- describe("renameAttachment function", () => {
+ describe('getAttachment', () => {
+ const uri = 'http://example.com/';
+ const token = 'test-token';
+ const objectId = 'test-object-id';
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should fetch attachment successfully', async () => {
+ const mockResponse = { data: 'some data' };
+ axios.get.mockResolvedValueOnce(mockResponse);
+
+ const response = await getAttachment(uri, token, objectId);
+
+ const expectedUrl =`${uri}browser/123/root?cmisselector=object&objectId=${objectId}&succinct=true`;
+ expect(axios.get).toHaveBeenCalledWith(expectedUrl, { headers: { Authorization: `Bearer ${token}` } });
+ expect(response).toBe(mockResponse);
+ });
+
+ it('should return null and log status text on error', async () => {
+ const errorMessage = 'Not Found';
+ const mockError = {
+ response: {
+ statusText: errorMessage,
+ },
+ };
+ axios.get.mockRejectedValueOnce(mockError);
+ console.log = jest.fn(); // Mock console.log
+
+ const response = await getAttachment(uri, token, objectId);
+
+ expect(console.log).toHaveBeenCalledWith(errorMessage);
+ expect(response).toBeNull();
+ });
+
+ it('should return null and log a default error message when there is no status text', async () => {
+ const mockError = {};
+ axios.get.mockRejectedValueOnce(mockError);
+ console.log = jest.fn(); // Mock console.log
+
+ const response = await getAttachment(uri, token, objectId);
+
+ expect(console.log).toHaveBeenCalledWith(errorMessage);
+ expect(response).toBeNull();
+ });
+ });
+
+ describe("renameAttachment", () => {
beforeEach(() => {
jest.clearAllMocks();
});
diff --git a/test/lib/sdm.test.js b/test/lib/sdm.test.js
index a01a34a..e5e9f44 100644
--- a/test/lib/sdm.test.js
+++ b/test/lib/sdm.test.js
@@ -9,9 +9,11 @@ const {
} = require("../../lib/util");
const {
getDraftAttachments,
+ getDraftAttachmentsForUpID,
getURLsToDeleteFromAttachments,
getURLFromAttachments,
getFolderIdForEntity,
+ updateAttachmentInDraft,
setRepositoryId
} = require("../../lib/persistence");
const {
@@ -21,21 +23,29 @@ const {
getFolderIdByPath,
createFolder,
deleteFolderWithAttachments,
+ getAttachment,
renameAttachment,
getRepositoryInfo
} = require("../../lib/handler");
const {
duplicateDraftFileErr,
- emptyFileErr,
+ virusFileErr,
+ duplicateFileErr,
+ otherFileErr,
+ userNotAuthorisedError,
+ userDoesNotHaveScopeMessage,
+ versionedRepositoryErr
} = require("../../lib/util/messageConsts");
jest.mock("@cap-js/attachments/lib/basic", () => class {});
jest.mock("../../lib/persistence", () => ({
getDraftAttachments: jest.fn(),
+ getDraftAttachmentsForUpID: jest.fn(),
getDuplicateAttachments: jest.fn(),
getURLsToDeleteFromAttachments: jest.fn(),
getURLFromAttachments: jest.fn(),
getFolderIdForEntity: jest.fn(),
+ updateAttachmentInDraft: jest.fn(),
getExistingAttachments: jest.fn(),
setRepositoryId: jest.fn()
}));
@@ -53,6 +63,7 @@ jest.mock("../../lib/handler", () => ({
getFolderIdByPath: jest.fn(),
createFolder: jest.fn(),
deleteFolderWithAttachments: jest.fn(),
+ getAttachment: jest.fn(),
renameAttachment: jest.fn(),
getRepositoryInfo: jest.fn()
}));
@@ -67,6 +78,72 @@ jest.mock("@sap/cds/lib", () => {
jest.mock("node-cache");
describe("SDMAttachmentsService", () => {
+ describe("checkRepositoryType", () => {
+ let service;
+ let cache;
+ let cds;
+
+ beforeEach(() => {
+ cds = require("@sap/cds/lib");
+ cache = new NodeCache();
+ NodeCache.mockImplementation(() => cache);
+ service = new SDMAttachmentsService();
+ service.creds = { clientId: "client-id", clientSecret: "client-secret" };
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it("should fetch repository info and check versioned status if not found in cache", async () => {
+ const mockReq = { reject: jest.fn() };
+ cds.context = {
+ user: {
+ tokenInfo: {
+ getPayload: jest.fn().mockReturnValue({ ext_attr: { zdn: "test-subdomain" } })
+ }
+ }
+ };
+
+ getConfigurations.mockReturnValue({ repositoryId: "repo123" });
+ cache.get.mockReturnValue(undefined);
+ getClientCredentialsToken.mockResolvedValue("mock-token");
+ getRepositoryInfo.mockResolvedValue({ data: "mock-repo-info" });
+ isRepositoryVersioned.mockResolvedValue(false);
+
+ await service.checkRepositoryType(mockReq);
+
+ expect(getClientCredentialsToken).toHaveBeenCalledWith(service.creds);
+ expect(getRepositoryInfo).toHaveBeenCalledWith(service.creds, "mock-token");
+ expect(isRepositoryVersioned).toHaveBeenCalledWith({ data: "mock-repo-info" }, "repo123");
+ expect(mockReq.reject).not.toHaveBeenCalled();
+ });
+
+ it("should reject the request if the repository is versioned", async () => {
+ const mockReq = { reject: jest.fn() };
+ cds.context = {
+ user: {
+ tokenInfo: {
+ getPayload: jest.fn().mockReturnValue({ ext_attr: { zdn: "test-subdomain" } })
+ }
+ }
+ };
+
+ getConfigurations.mockReturnValue({ repositoryId: "repo123" });
+ cache.get.mockReturnValue(undefined);
+ getClientCredentialsToken.mockResolvedValue("mock-token");
+ getRepositoryInfo.mockResolvedValue({ data: "mock-repo-info" });
+ isRepositoryVersioned.mockResolvedValue(true);
+
+ await service.checkRepositoryType(mockReq);
+
+ expect(getClientCredentialsToken).toHaveBeenCalledWith(service.creds);
+ expect(getRepositoryInfo).toHaveBeenCalledWith(service.creds, "mock-token");
+ expect(isRepositoryVersioned).toHaveBeenCalledWith({ data: "mock-repo-info" }, "repo123");
+ expect(mockReq.reject).toHaveBeenCalledWith(400, versionedRepositoryErr);
+ });
+ });
+
describe("Test get method", () => {
let service;
let repoInfo
@@ -103,8 +180,8 @@ describe("SDMAttachmentsService", () => {
},
},
};
- cds = require("@sap/cds/lib");
-cds.context = {
+ let cds = require("@sap/cds/lib");
+ cds.context = {
user: {
tokenInfo: {
getPayload: jest.fn(() => ({
@@ -114,7 +191,7 @@ cds.context = {
})),
},
},
- };
+ };
const attachments = ["attachment1", "attachment2"];
const keys = ["key1", "key2"];
const response = { url: "mockUrl" };
@@ -145,7 +222,7 @@ cds.context = {
},
},
};
- cds = require("@sap/cds/lib");
+ let cds = require("@sap/cds/lib");
cds.context = {
user: {
tokenInfo: {
@@ -185,69 +262,6 @@ cds.context = {
);
});
- it("should throw error if repositoryId is versioned", async () => {
- const req = {
- user: {
- tokenInfo: {
- getTokenValue: jest.fn().mockReturnValue("tokenValue"),
- },
- },
- reject: jest.fn(),
- };
- cds = require("@sap/cds/lib");
- cds.context = {
- user: {
- tokenInfo: {
- getPayload: jest.fn(() => ({
- ext_attr: {
- zdn: 'subdomain' // simulate the subdomain extraction
- }
- })),
- },
- },
- };
- const attachments = ["attachment1", "attachment2"];
- const keys = ["key1", "key2"];
- isRepositoryVersioned.mockResolvedValue(true);
- await service.get(attachments, keys, req);
- expect(req.reject).toHaveBeenCalledWith(
- 400,
- "Attachments are not supported for a versioned repository."
- );
- })
-
- it("should throw error if cache returns repositoryId is versioned", async () => {
- NodeCache.prototype.get.mockImplementation(() => "versioned");
- const req = {
- user: {
- tokenInfo: {
- getTokenValue: jest.fn().mockReturnValue("tokenValue"),
- },
- },
- reject: jest.fn(),
- };
- cds = require("@sap/cds/lib");
- cds.context = {
- user: {
- tokenInfo: {
- getPayload: jest.fn(() => ({
- ext_attr: {
- zdn: 'subdomain' // simulate the subdomain extraction
- }
- })),
- },
- },
- };
- const attachments = ["attachment1", "attachment2"];
- const keys = ["key1", "key2"];
- isRepositoryVersioned.mockResolvedValue(true);
- await service.get(attachments, keys, req);
- expect(req.reject).toHaveBeenCalledWith(
- 400,
- "Attachments are not supported for a versioned repository."
- );
- })
-
it("should interact with DB, fetch access token and readAttachment with correct parameters when cache returns non-versioned repo type", async () => {
NodeCache.prototype.get.mockImplementation(() => "non-versioned");
const req = {
@@ -257,8 +271,8 @@ cds.context = {
},
},
};
- cds = require("@sap/cds/lib");
-cds.context = {
+ let cds = require("@sap/cds/lib");
+ cds.context = {
user: {
tokenInfo: {
getPayload: jest.fn(() => ({
@@ -268,7 +282,7 @@ cds.context = {
})),
},
},
- };
+ };
const attachments = ["attachment1", "attachment2"];
const keys = ["key1", "key2"];
const response = { url: "mockUrl" };
@@ -291,192 +305,303 @@ cds.context = {
);
});
});
-
- describe("draftSaveHandler", () => {
+
+ describe('renameHandler', () => {
let service;
- let mockReq;
- let cds;
- let repoInfo;
+ let req;
+ let token;
+
beforeEach(() => {
- NodeCache.prototype.get.mockClear();
jest.clearAllMocks();
cds = require("@sap/cds/lib");
service = new SDMAttachmentsService();
- service.creds = { uaa: "mocked uaa" };
- repoInfo = {
- data: {
- "123": {
- capabilities: {
- "capabilityContentStreamUpdatability": "pwconly"
- }
- }
- }
- }
- mockReq = {
+ getConfigurations.mockReturnValue({ repositoryId: 'repo123' });
+ service.creds = {
+ uri: 'sampleUri'
+ };
+ req = {
query: {
target: {
- name: "testName",
- },
+ name: 'sampleTarget'
+ }
},
user: {
tokenInfo: {
- getTokenValue: jest.fn().mockReturnValue("mocked_token"),
- },
+ getTokenValue: jest.fn().mockReturnValue('sampleTokenValue')
+ }
},
- reject: jest.fn(),
- info: jest.fn(),
warn: jest.fn()
};
- cds = require("@sap/cds/lib");
- cds.model.definitions[mockReq.query.target.name + ".attachments"] = {
- keys: {
- up_: {
- keys: [{ ref: ["attachment"] }],
- },
- },
- };
- cds.context = {
- user: {
- tokenInfo: {
- getPayload: jest.fn(() => ({
- ext_attr: {
- zdn: 'subdomain' // simulate the subdomain extraction
- }
- })),
- },
- },
- };
- NodeCache.prototype.get.mockImplementation(() => undefined);
- getConfigurations.mockReturnValue({
- repositoryId: 'mockRepositoryId',
+ token = 'sampleAccessToken';
+ });
+
+ it('should rename modified attachments', async () => {
+ service.checkRepositoryType = jest.fn().mockResolvedValue();
+ service.isFileNameDuplicateInDrafts = jest.fn().mockResolvedValue();
+ service.getAttachementDataInSDM = jest.fn((uri, token, objectId) => {
+ if (objectId === 'url2') {
+ return { filename: 'sampleFileName', folderId: 'sampleFolderId' };
+ }
+ return { filename: 'prevFile1', folderId: 'sampleFolderId' };
});
- getRepositoryInfo.mockResolvedValueOnce(repoInfo);
- isRepositoryVersioned.mockResolvedValueOnce(false);
+ service.rename = jest.fn().mockResolvedValue('error occurred');
+
+ fetchAccessToken.mockResolvedValue(token);
+ getDraftAttachments.mockResolvedValue([
+ { ID: 1, HasActiveEntity: true, filename: 'file1', url: 'url1' },
+ { ID: 2, HasActiveEntity: false, filename: 'fileDraft', url: 'url2' }
+ ]);
+ checkAttachmentsToRename.mockResolvedValue([{ ID: 1, url: 'url1', name: 'file1', prevname: 'prevFile1', folderId: 'sampleFolderId' }]);
+
+ await service.renameHandler(req);
+
+ expect(service.checkRepositoryType).toHaveBeenCalledWith(req);
+ expect(service.isFileNameDuplicateInDrafts).toHaveBeenCalled();
+ expect(fetchAccessToken).toHaveBeenCalledWith(service.creds, 'sampleTokenValue');
+ expect(getDraftAttachments).toHaveBeenCalledWith(cds.model.definitions['sampleTarget.attachments'], req, undefined);
+ expect(service.getAttachementDataInSDM).toHaveBeenCalledWith(service.creds.uri, token, 'url2');
+ expect(checkAttachmentsToRename).toHaveBeenCalled();
+ expect(service.rename).toHaveBeenCalledWith(
+ [{ ID: 1, url: 'url1', name: 'file1', prevname: 'prevFile1', folderId: 'sampleFolderId' },
+ { ID: 2, url: 'url2', name: 'fileDraft', prevname: 'sampleFileName', folderId: 'sampleFolderId' }],
+ token,
+ req
+ );
+ expect(req.warn).toHaveBeenCalledWith(500, 'error occurred');
});
-
- it("draftSaveHandler() should do nothing when getDraftAttachments() returns empty array", async () => {
- getDraftAttachments.mockResolvedValueOnce([]);
-
- await service.draftSaveHandler(mockReq);
-
- expect(getDraftAttachments).toHaveBeenCalledTimes(1);
- expect(fetchAccessToken).toHaveBeenCalledTimes(0);
+
+ it('should not rename if no attachments are modified', async () => {
+ service.checkRepositoryType = jest.fn().mockResolvedValue();
+ service.isFileNameDuplicateInDrafts = jest.fn().mockResolvedValue();
+ service.getAttachementDataInSDM = jest.fn().mockResolvedValue({ filename: 'fileDraft', folderId: 'folderId' });
+ service.rename = jest.fn();
+
+ fetchAccessToken.mockResolvedValue(token);
+ getDraftAttachments.mockResolvedValue([]);
+ checkAttachmentsToRename.mockResolvedValue([]);
+
+ await service.renameHandler(req);
+
+ expect(service.checkRepositoryType).toHaveBeenCalledWith(req);
+ expect(service.isFileNameDuplicateInDrafts).not.toHaveBeenCalled();
+ expect(fetchAccessToken).not.toHaveBeenCalled();
+ expect(getDraftAttachments).toHaveBeenCalledWith(cds.model.definitions['sampleTarget.attachments'], req, undefined);
+ expect(service.getAttachementDataInSDM).not.toHaveBeenCalled();
+ expect(checkAttachmentsToRename).not.toHaveBeenCalled();
+ expect(service.rename).not.toHaveBeenCalled();
+ expect(req.warn).not.toHaveBeenCalled();
});
- it("should not call onCreate if no draft attachments are available", async () => {
- getDraftAttachments.mockResolvedValueOnce([]);
- const createSpy = jest.spyOn(service, "create");
- await service.draftSaveHandler(mockReq);
-
- expect(createSpy).not.toBeCalled();
+ it('should not modify attachments if filenameInDraft equals filenameInSDM', async () => {
+ service.checkRepositoryType = jest.fn().mockResolvedValue();
+ service.isFileNameDuplicateInDrafts = jest.fn().mockResolvedValue();
+ service.getAttachementDataInSDM = jest.fn().mockResolvedValue({ filename: 'fileDraft', folderId: 'sampleFolderId' });
+ service.rename = jest.fn().mockResolvedValue('');
+
+ fetchAccessToken.mockResolvedValue(token);
+ getDraftAttachments.mockResolvedValue([
+ { ID: 1, HasActiveEntity: true, filename: 'file1', url: 'url1' },
+ { ID: 2, HasActiveEntity: false, filename: 'fileDraft', url: 'url2' }
+ ]);
+ checkAttachmentsToRename.mockResolvedValue([]);
+
+ await service.renameHandler(req);
+
+ expect(service.checkRepositoryType).toHaveBeenCalledWith(req);
+ expect(service.isFileNameDuplicateInDrafts).toHaveBeenCalled();
+ expect(fetchAccessToken).toHaveBeenCalledWith(service.creds, 'sampleTokenValue');
+ expect(getDraftAttachments).toHaveBeenCalledWith(cds.model.definitions['sampleTarget.attachments'], req, undefined);
+ expect(service.getAttachementDataInSDM).toHaveBeenCalledWith(service.creds.uri, token, 'url2');
+ expect(checkAttachmentsToRename).toHaveBeenCalled();
+ expect(req.warn).not.toHaveBeenCalled();
});
- it("should handle successful create without any issue", async () => {
- service.create = jest.fn().mockResolvedValueOnce([]);
- const createSpy = jest.spyOn(service, "create");
- getDraftAttachments.mockResolvedValueOnce([{'HasActiveEntity':false}]);
- checkAttachmentsToRename.mockResolvedValueOnce([]);
+ it('should avoid renaming if there are no modified attachments', async () => {
+ service.checkRepositoryType = jest.fn().mockResolvedValue();
+ service.isFileNameDuplicateInDrafts = jest.fn().mockResolvedValue();
+ service.getAttachementDataInSDM = jest.fn().mockResolvedValue({ filename: 'fileDraft', folderId: 'sampleFolderId' });
+ service.rename = jest.fn().mockResolvedValue('');
+
+ fetchAccessToken.mockResolvedValue(token);
+ getDraftAttachments.mockResolvedValue([
+ { ID: 1, HasActiveEntity: true, filename: 'file1', url: 'url1' }
+ ]);
+ checkAttachmentsToRename.mockResolvedValue([]);
+
+ await service.renameHandler(req);
+
+ expect(service.checkRepositoryType).toHaveBeenCalledWith(req);
+ expect(service.isFileNameDuplicateInDrafts).toHaveBeenCalled();
+ expect(fetchAccessToken).toHaveBeenCalledWith(service.creds, 'sampleTokenValue');
+ expect(checkAttachmentsToRename).toHaveBeenCalled();
+ expect(service.rename).not.toHaveBeenCalled();
+ expect(req.warn).not.toHaveBeenCalled();
+ });
+ });
- await service.draftSaveHandler(mockReq);
+ describe('getAttachementDataInSDM', () => {
+ let service;
+ const uri = 'someUri';
+ const token = 'someToken';
+ const objectId = 'someObjectId';
- expect(createSpy).toBeCalled();
- expect(mockReq.warn).not.toBeCalled();
+ beforeEach(() => {
+ jest.clearAllMocks();
+ service = new SDMAttachmentsService();
});
-
- it("should handle successful onRename without any issue", async () => {
- service.rename = jest.fn().mockResolvedValueOnce([]);
- const renameSpy = jest.spyOn(service, "rename");
- getDraftAttachments.mockResolvedValueOnce([
- {
- 'ID': 'id1',
- 'filename': 'nameprev',
- 'HasActiveEntity' : true
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should return formatted attachment data correctly', async () => {
+ // Arrange
+ const mockResponse = {
+ data: {
+ succinctProperties: {
+ 'cmis:name': 'testFileName.docx',
+ 'sap:parentIds': ['parentId123'],
+ },
},
- {
- 'ID': 'id2',
- 'filename': 'samename',
- 'HasActiveEntity' : true
- }]);
- checkAttachmentsToRename.mockResolvedValueOnce([{}]);
-
- await service.draftSaveHandler(mockReq);
-
- expect(renameSpy).toBeCalled();
- expect(mockReq.warn).not.toBeCalled();
+ };
+ getAttachment.mockResolvedValue(mockResponse);
+
+ // Act
+ const result = await service.getAttachementDataInSDM(uri, token, objectId);
+
+ // Assert
+ expect(result).toEqual({
+ filename: 'testFileName.docx',
+ folderId: 'parentId123',
+ });
});
-
- it("should not call rename if no draft attachments are available", async () => {
- getDraftAttachments.mockResolvedValueOnce([]);
- const renameSpy = jest.spyOn(service, "rename");
- await service.draftSaveHandler(mockReq);
-
- expect(renameSpy).not.toBeCalled();
+
+ it('should throw an error if getAttachment throws an error', async () => {
+ // Arrange
+ const mockError = new Error('Some error');
+ getAttachment.mockRejectedValue(mockError);
+
+ // Act & Assert
+ await expect(service.getAttachementDataInSDM(uri, token, objectId)).rejects.toThrow('Some error');
});
-
- it("should call only onCreate if only new attachments are available in draft", async () => {
- service.create = jest.fn().mockResolvedValueOnce([]);
- const createSpy = jest.spyOn(service, "create");
- const renameSpy = jest.spyOn(service, "rename");
- getDraftAttachments.mockResolvedValueOnce([{'HasActiveEntity' : false}]);
- checkAttachmentsToRename.mockResolvedValueOnce([]);
-
- await service.draftSaveHandler(mockReq);
-
- expect(createSpy).toBeCalled();
- expect(renameSpy).not.toBeCalled();
+
+ it('should return undefined folderId if parentIds array is empty', async () => {
+ // Arrange
+ const mockResponse = {
+ data: {
+ succinctProperties: {
+ 'cmis:name': 'testFileName.docx',
+ 'sap:parentIds': [],
+ },
+ },
+ };
+ getAttachment.mockResolvedValue(mockResponse);
+
+ // Act
+ const result = await service.getAttachementDataInSDM(uri, token, objectId);
+
+ // Assert
+ expect(result).toEqual({
+ filename: 'testFileName.docx',
+ folderId: undefined,
+ });
});
+ });
- it("should call only onRename if only modified attachments are available in draft", async () => {
- service.rename = jest.fn().mockResolvedValueOnce([]);
- const createSpy = jest.spyOn(service, "create");
- const renameSpy = jest.spyOn(service, "rename");
- getDraftAttachments.mockResolvedValueOnce([
- {
- 'ID': 'id',
- 'filename': 'nameprev',
- 'HasActiveEntity' : true
- }]);
- checkAttachmentsToRename.mockResolvedValueOnce([{}]);
+ describe('draftSaveHandler', () => {
+ let service;
+ beforeEach(() => {
+ jest.clearAllMocks();
+ service = new SDMAttachmentsService();
+ getConfigurations.mockReturnValue({ repositoryId: 'repo123' });
+ service.checkRepositoryType = jest.fn();
+ service.isFileNameDuplicateInDrafts = jest.fn();
+ service.create = jest.fn();
+ service.creds = {};
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ test('should skip when req.data.content is not provided', async () => {
+ const req = { data: {} };
+ await service.draftSaveHandler(req);
+ expect(service.checkRepositoryType).not.toHaveBeenCalled();
+ });
+
+ test('should handle drafts when attachment values are found', async () => {
+ const draftAttachments = [];
+ const req = { data: { content: 'some content', ID: '12345' }, target: draftAttachments, user: { tokenInfo: { getTokenValue: jest.fn().mockReturnValue('mockTokenValue') } } };
+ const token = 'token123';
+ const attachment_val = [
+ { HasActiveEntity: false, ID: '12345' },
+ { HasActiveEntity: true, ID: '67890' },
+ ];
+ getDraftAttachmentsForUpID.mockResolvedValue(attachment_val);
+ fetchAccessToken.mockResolvedValue(token);
+
+ await service.draftSaveHandler(req);
- await service.draftSaveHandler(mockReq);
-
- expect(createSpy).not.toBeCalled();
- expect(renameSpy).toBeCalled();
+ expect(service.isFileNameDuplicateInDrafts).toHaveBeenCalledWith(attachment_val, req);
+ expect(service.create).toHaveBeenCalledWith([{ ...attachment_val[0], content: 'some content' }], draftAttachments, req, token);
+ expect(req.data.content).toBeNull();
+ });
+
+ test('should not create attachment if no matching inactive entities are found', async () => {
+ const draftAttachments = [];
+ const req = { data: { content: 'some content', ID: '12345' }, target: draftAttachments, user: { tokenInfo: { getTokenValue: jest.fn().mockReturnValue('mockTokenValue') } } };
+ const token = 'token123';
+ const attachment_val = [{ HasActiveEntity: true, ID: '12345' }];
+
+ getDraftAttachmentsForUpID.mockResolvedValue(attachment_val);
+ fetchAccessToken.mockResolvedValue(token);
+
+ await service.draftSaveHandler(req);
+
+ expect(service.create).not.toHaveBeenCalled();
+ expect(req.data.content).toBeNull();
});
- it("should call both onRename and onCreate if both modified and new attachments are available in draft", async () => {
- service.rename = jest.fn().mockResolvedValueOnce([]);
- service.create = jest.fn().mockResolvedValueOnce([]);
- const createSpy = jest.spyOn(service, "create");
- const renameSpy = jest.spyOn(service, "rename");
- getDraftAttachments.mockResolvedValueOnce([
- {
- 'ID': 'id',
- 'filename': 'nameprev',
- 'HasActiveEntity' : true
- },
- {
- 'HasActiveEntity' : false
- }]);
- checkAttachmentsToRename.mockResolvedValueOnce([{}]);
-
- await service.draftSaveHandler(mockReq);
+ test('should skip when no attachments are found', async () => {
+ const draftAttachments = [];
+ const req = { data: { content: 'some content', ID: '12345' }, target: draftAttachments, user: { tokenInfo: { getTokenValue: jest.fn().mockReturnValue('mockTokenValue') } } };
+ const attachment_val = [];
+
+ getDraftAttachmentsForUpID.mockResolvedValue(attachment_val);
+
+ await service.draftSaveHandler(req);
+
+ expect(service.isFileNameDuplicateInDrafts).not.toHaveBeenCalled();
+ expect(service.create).not.toHaveBeenCalled();
+ expect(req.data.content).toBeNull();
+ });
- expect(createSpy).toBeCalled();
- expect(renameSpy).toBeCalled();
+ test('should skip processing when req.data.content is null after initial check', async () => {
+ const draftAttachments = [];
+ const req = { data: { content: null, ID: '12345' }, target: draftAttachments, user: { tokenInfo: { getTokenValue: jest.fn().mockReturnValue('mockTokenValue') } } };
+ const attachment_val = [
+ { HasActiveEntity: false, ID: '12345' },
+ { HasActiveEntity: true, ID: '67890' },
+ ];
+ getDraftAttachmentsForUpID.mockResolvedValue(attachment_val);
+
+ req.data.content = null; // simulating content being reset to null after initial check
+
+ await service.draftSaveHandler(req);
+
+ expect(service.isFileNameDuplicateInDrafts).not.toHaveBeenCalled();
+ expect(service.create).not.toHaveBeenCalled();
});
});
- describe("Test filterAttachments", () => {
+ describe("filterAttachments", () => {
let service;
- let mockReq;
+ let mockedReq;
beforeEach(() => {
jest.clearAllMocks();
service = new SDMAttachmentsService();
- getConfigurations.mockReturnValue({
- repositoryId: 'mockRepositoryId',
- });
- mockReq = {
+ mockedReq = {
query: {
SELECT: {},
},
@@ -485,13 +610,16 @@ cds.context = {
getTokenValue: jest.fn().mockReturnValue("mocked_token"),
}
}
- }
+ };
+ getConfigurations.mockReturnValue({
+ repositoryId: 'mockRepositoryId',
+ });
});
it("should add a condition to filter attachments by repositoryId when where clause is empty", async() => {
- mockReq.query.SELECT.where = [];
- await service.filterAttachments(mockReq);
- expect(mockReq.query.SELECT.where).toEqual([
+ mockedReq.query.SELECT.where = [];
+ await service.filterAttachments(mockedReq);
+ expect(mockedReq.query.SELECT.where).toEqual([
{ ref: ['repositoryId'] },
'=',
{ val: "mockRepositoryId" }
@@ -499,9 +627,9 @@ cds.context = {
});
it("should add a condition to filter attachments by repositoryId when where clause already exists", async() => {
- mockReq.query.SELECT.where = [{ ref: ['someField'] }, '=', { val: 'someValue' }];
- await service.filterAttachments(mockReq);
- expect(mockReq.query.SELECT.where).toEqual([
+ mockedReq.query.SELECT.where = [{ ref: ['someField'] }, '=', { val: 'someValue' }];
+ await service.filterAttachments(mockedReq);
+ expect(mockedReq.query.SELECT.where).toEqual([
{ ref: ['someField'] },
'=',
{ val: 'someValue' },
@@ -513,8 +641,8 @@ cds.context = {
});
it("should add a condition to filter attachments by repositoryId when where clause doesn't exist", async() => {
- await service.filterAttachments(mockReq);
- expect(mockReq.query.SELECT.where).toEqual([
+ await service.filterAttachments(mockedReq);
+ expect(mockedReq.query.SELECT.where).toEqual([
{ ref: ['repositoryId'] },
'=',
{ val: "mockRepositoryId" }
@@ -522,13 +650,12 @@ cds.context = {
});
});
- describe("Test setRepository", () => {
+ describe("setRepository", () => {
let service;
beforeEach(() => {
jest.clearAllMocks();
service = new SDMAttachmentsService();
- cds = require("@sap/cds/lib");
getConfigurations.mockReturnValue({
repositoryId: 'mockRepositoryId',
@@ -789,7 +916,6 @@ cds.context = {
let service;
beforeEach(() => {
jest.clearAllMocks();
- cds = require("@sap/cds/lib");
service = new SDMAttachmentsService();
});
it("should delete attachments if req.attachmentsToDelete has records to delete", async () => {
@@ -956,44 +1082,6 @@ cds.context = {
expect(onCreateSpy).toBeCalled();
expect(getParentIdSpy).toBeCalled();
- expect(mockReq.warn).not.toBeCalled();
- })
-
- it("should handle failure in onCreate", async () => {
- const attachment_val_create = [{}];
- const token = "token";
- const attachments = [];
-
- service.getParentId = jest.fn().mockResolvedValueOnce("parentId");
- service.onCreate = jest.fn().mockResolvedValue([{typeOfError:'duplicate',name:'sample.pdf'},{typeOfError:'virus',name:'virus.pdf'},{typeOfError:'other',message:'Child invalid.pdf with Id abc is not a valid file type'}]);
-
- let response = await service.create(
- attachment_val_create,
- attachments,
- mockReq,
- token
- );
-
- expect(response).toBe("The following files contain potential malware and cannot be uploaded:\n• virus.pdf\nThe following files could not be uploaded as they already exist:\n• sample.pdf\nChild invalid.pdf with Id abc is not a valid file type\n");
- })
-
- it("should handle failure in onCreate after failure in rename", async () => {
- const attachment_val_create = [{}];
- const token = "token";
- const attachments = [];
-
- service.getParentId = jest.fn().mockResolvedValueOnce("parentId");
- service.onCreate = jest.fn().mockResolvedValue([{typeOfError:'duplicate',name:'sample.pdf'},{typeOfError:'virus',name:'virus.pdf'},{typeOfError:'other',message:'Child invalid.pdf with Id abc is not a valid file type'}]);
-
- let response = await service.create(
- attachment_val_create,
- attachments,
- mockReq,
- token,
- "rename_error\n"
- );
-
- expect(response).toBe("rename_error\nThe following files contain potential malware and cannot be uploaded:\n• virus.pdf\nThe following files could not be uploaded as they already exist:\n• sample.pdf\nChild invalid.pdf with Id abc is not a valid file type\n");
})
});
@@ -1078,132 +1166,71 @@ cds.context = {
})
});
- describe("onCreate", () => {
- let service;
+ describe('onCreate', () => {
+ let data, credentials, token, req, parentId, service;
+
beforeEach(() => {
jest.clearAllMocks();
service = new SDMAttachmentsService();
- });
-
- it("should return empty array if no attachments fail", async () => {
- const data = [{ ID: 1 }];
- const credentials = {};
- const token = "token";
- const attachments = [];
- const req = {
- data: { attachments: [...data] },
- user: {
- tokenInfo: {
- getTokenValue: jest.fn().mockReturnValue("tokenValue"),
- },
- },
+ getConfigurations.mockReturnValue({ repositoryId: 'repo123' });
+ data = [{ filename: 'file1' }];
+ credentials = { user: 'user', pass: 'pass' };
+ token = 'token';
+ req = {
+ reject: jest.fn(),
};
-
+ parentId = 'parent123';
+ });
+
+ it('should successfully create attachments and update draft', async () => {
createAttachment
.mockResolvedValueOnce({
status: 201,
- data: { succinctProperties: { "cmis:objectId": "url" } },
- })
-
- const result = await service.onCreate(
- data,
- credentials,
- token,
- attachments,
- req,
- createAttachment
- );
- expect(result).toEqual([]);
+ data: { succinctProperties: { 'cmis:objectId': 'url1' } },
+ });
+ updateAttachmentInDraft.mockResolvedValue(true);
+
+ await service.onCreate(data, credentials, token, req, parentId);
+
+ expect(createAttachment).toHaveBeenCalledTimes(1);
+ expect(updateAttachmentInDraft).toHaveBeenCalledTimes(1);
+ expect(req.reject).not.toHaveBeenCalled();
});
-
- it("onCreate() should add error message to failedReq if d.content is null", async () => {
- const mockAttachments = [
- { content: null, filename: "filename1", ID: "id1" },
- { content: "valid_data", filename: "filename2", ID: "id2" },
- ];
- const mockReq = {
- data: {
- attachments: [...mockAttachments],
- user: {
- tokenInfo: {
- getTokenValue: jest.fn().mockReturnValue("tokenValue"),
- },
- },
- },
- };
- const token = "mocked_token";
- const credentials = "mocked_credentials";
- const attachments = "mocked_attachments";
- const parentId = "mocked_parentId";
-
- createAttachment.mockResolvedValueOnce({
- status: 201,
- data: { succinctProperties: { "cmis:objectId": "some_object_id" } },
+
+ it('should reject when a virus is found in the file', async () => {
+ createAttachment
+ .mockResolvedValueOnce({
+ status: 500,
+ response: { data: { message: "Malware Service Exception: Virus found in the file!" } }
});
-
- const failedFiles = await service.onCreate(
- mockAttachments,
- credentials,
- token,
- attachments,
- mockReq,
- parentId
- );
-
- expect(failedFiles).toEqual([emptyFileErr("filename1")]);
- expect(mockReq.data.attachments).toHaveLength(1);
- expect(mockReq.data.attachments[0]).toEqual({
- content: null,
- filename: "filename2",
- ID: "id2",
- folderId: parentId,
- repositoryId: "mockRepositoryId",
- url: "some_object_id",
+
+ await service.onCreate(data, credentials, token, req, parentId);
+
+ expect(req.reject).toHaveBeenCalledWith(virusFileErr(['file1']));
+ });
+
+ it('should reject when there is a name constraint violation', async () => {
+ createAttachment
+ .mockResolvedValueOnce({
+ status: 500,
+ response: { data: { exception: "nameConstraintViolation" } }
});
+
+ await service.onCreate(data, credentials, token, req, parentId);
+
+ expect(req.reject).toHaveBeenCalledWith(duplicateFileErr(['file1']));
});
-
- it("should return failed request messages if some attachments fail", async () => {
- const data = [{ ID: 1 }, { ID: 2 }, { ID: 3}, { ID: 4}];
- const credentials = {};
- const token = "token";
- const attachments = [];
- const req = {
- data: { attachments: [...data] },
- user: {
- tokenInfo: {
- getTokenValue: jest.fn().mockReturnValue("tokenValue"),
- },
- },
- };
-
+
+ it('should reject when another error occurs', async () => {
createAttachment
- .mockResolvedValueOnce({
- status: 201,
- data: { succinctProperties: { "cmis:objectId": "url" } },
- })
- .mockResolvedValueOnce({
- status: 400,
- response: { data: { exception: "nameConstraintViolation" } },
- })
- .mockResolvedValueOnce({
- status: 500,
- response: { data: { message: "Malware Service Exception: Virus found in the file!" } }
- })
- .mockResolvedValueOnce({
- status: 500,
- response: { data: { message: "Invalid file type" } }
- });
-
- const result = await service.onCreate(
- data,
- credentials,
- token,
- attachments,
- req,
- createAttachment
- );
- expect(result).toEqual([{ typeOfError:'duplicate', "name": undefined }, { typeOfError:'virus', "name": undefined }, { typeOfError:'other', message: "Invalid file type" }]);
- expect(req.data.attachments).toHaveLength(1);
+ .mockResolvedValueOnce({
+ status: 500,
+ response: { data: { exception: "some other error" } }
+ });
+
+ await service.onCreate(data, credentials, token, req, parentId);
+
+ expect(req.reject).toHaveBeenCalledWith(otherFileErr(['file1']));
});
});
@@ -1299,6 +1326,7 @@ cds.context = {
beforeEach(() => {
jest.clearAllMocks();
cds = require("@sap/cds/lib");
+ getConfigurations.mockReturnValue({ repositoryId: 'repo123' });
service = new SDMAttachmentsService();
service.creds = { uaa: "mocked uaa" };
mockReq = {
@@ -1366,19 +1394,45 @@ cds.context = {
);
});
- it("Success in getParentId", async () => {
- let attachments = cds.model.definitions[mockReq.query.target.name + ".attachments"]
- let token = "mocked_token"
- getFolderIdForEntity.mockResolvedValueOnce(["folderId"]);
+ it("getParentId should reject with 403 if createFolder response status is 403 and message matches userDoesNotHaveScopeMessage", async () => {
+ let attachments = cds.model.definitions[mockReq.query.target.name + ".attachments"];
+ let token = "mocked_token";
+ getFolderIdForEntity.mockResolvedValueOnce([]);
+ getFolderIdByPath.mockResolvedValueOnce(null);
+ createFolder.mockResolvedValueOnce({
+ status: 403,
+ response: {
+ data: userDoesNotHaveScopeMessage
+ }
+ });
- await service.getParentId(attachments,mockReq,token)
-
- expect(getFolderIdForEntity).toHaveBeenCalledWith(
- cds.model.definitions[mockReq.query.target.name + ".attachments"],
- mockReq,
- "mockRepositoryId"
- );
- });
+ try {
+ await service.getParentId(attachments, mockReq, token);
+ } catch (err) {
+ console.error("Error in getParentId:", err);
+ }
+
+ expect(mockReq.reject).toHaveBeenCalledWith(403, userNotAuthorisedError);
+ });
+
+ it("getParentId should return parentId if folderId is not null in folderIds", async () => {
+ let attachments = cds.model.definitions[mockReq.query.target.name + ".attachments"];
+ let token = "mocked_token";
+
+ const folderIds = [
+ { folderId: null },
+ { folderId: "mock_folder_id_1" },
+ { folderId: "mock_folder_id_2" }
+ ];
+
+ getFolderIdForEntity.mockResolvedValueOnce(folderIds);
+
+ const parentId = await service.getParentId(attachments, mockReq, token);
+
+ expect(parentId).toEqual("mock_folder_id_1");
+ expect(getFolderIdByPath).not.toHaveBeenCalled();
+ expect(createFolder).not.toHaveBeenCalled();
+ });
});
describe("isFileNameDuplicateInDrafts", () => {
@@ -1387,7 +1441,6 @@ cds.context = {
beforeEach(() => {
jest.clearAllMocks();
- cds = require("@sap/cds/lib");
service = new SDMAttachmentsService();
mockReq = {
query: {
@@ -1477,6 +1530,20 @@ cds.context = {
});
});
+ describe('getStatus', () => {
+ let service;
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ service = new SDMAttachmentsService();
+ });
+
+ it('should return the status as "Clean"', async () => {
+ const status = await service.getStatus();
+ expect(status).toBe("Clean");
+ });
+ });
+
describe("registerUpdateHandlers", () => {
let mockSrv;
let service;
@@ -1516,5 +1583,24 @@ cds.context = {
expect.any(Function)
);
});
+ it("should call srv.before for PUT with correct target.drafts and callback", () => {
+ const target = { drafts: "drafts" };
+ service.registerUpdateHandlers(mockSrv, "entity", target);
+ expect(mockSrv.before).toHaveBeenCalledWith(
+ "PUT",
+ target.drafts,
+ expect.any(Function)
+ );
+ });
+
+ it("should not call srv.before for PUT when target.drafts is not defined", () => {
+ const target = {};
+ service.registerUpdateHandlers(mockSrv, "entity", target);
+ expect(mockSrv.before).not.toHaveBeenCalledWith(
+ "PUT",
+ undefined,
+ expect.any(Function)
+ );
+ });
});
});
\ No newline at end of file