Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions _i18n/messages.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
AttachmentNotFound=Attachment not found.
UnableToDownloadAttachmentScanStatusNotClean=Unable to download the attachment as scan status is not clean.
InvalidContentSize=Invalid Content Size.
AttachmentSizeExceeded=File Size limit exceeded beyond 400 MB.
MultiUpdateNotSupported=Multi update is not supported.
8 changes: 4 additions & 4 deletions lib/generic-handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ async function validateAttachment(req) {
const AttachmentsSrv = await cds.connect.to("attachments")
const status = await AttachmentsSrv.getStatus(req.target, { ID: req.data.ID || req.params?.at(-1).ID })
if (status === null || status === undefined) {
return req.reject(404)
return req.reject(404, 'AttachmentNotFound')
}
const scanEnabled = cds.env.requires?.attachments?.scan ?? true
if (scanEnabled && status !== 'Clean') {
req.reject(403, 'Unable to download the attachment as scan status is not clean.')
req.reject(403, 'UnableToDownloadAttachmentScanStatusNotClean')
}
}
}
Expand Down Expand Up @@ -78,10 +78,10 @@ function validateAttachmentSize(req) {
fileSizeInBytes = Number(contentLengthHeader)
const MAX_FILE_SIZE = 419430400 //400 MB in bytes
if (fileSizeInBytes > MAX_FILE_SIZE) {
return req.reject(400, "File Size limit exceeded beyond 400 MB.")
return req.reject(400, "AttachmentSizeExceeded")
}
} else {
return req.reject(400, "Invalid Content Size")
return req.reject(400, "InvalidContentSize")
}
}

Expand Down
4 changes: 2 additions & 2 deletions srv/object-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ module.exports = class RemoteAttachmentsService extends require("./basic") {

let metadata = await srv.run(SELECT.from(req.subject).columns('url', ...Object.keys(req.target.keys)))
if (metadata.length > 1) {
return req.error(501, 'MULTI_UPDATE_NOT_SUPPORTED')
return req.error(501, 'MultiUpdateNotSupported')
}
metadata = metadata[0]
if (!metadata) {
return req.error(404)
return req.error(404, 'AttachmentNotFound')
}
req.data.ID = metadata.ID
req.data.url ??= metadata.url
Expand Down
36 changes: 36 additions & 0 deletions tests/integration/attachments.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,42 @@ describe("Tests for uploading/deleting attachments through API calls", () => {
})
})

it("should fail to upload attachment to non-existent entity", async () => {
try {
const fileContent = fs.readFileSync(
path.join(__dirname, "..", "integration", "content/sample.pdf")
)
await axios.put(
`/odata/v4/admin/Incidents(${incidentID})/attachments(up__ID=${incidentID},ID=${cds.utils.uuid()})/content`,
fileContent,
{
headers: {
"Content-Type": "application/pdf",
"Content-Length": fileContent.length,
},
}
)
expect.fail("Expected 404 error")
} catch (err) {
expect(err.response.status).to.equal(404)
expect(err.response.data.error.code).to.equal('Attachment not found')
}
})

it("should fail to update note for non-existent attachment", async () => {
try {
await axios.patch(
`/odata/v4/admin/Incidents(${incidentID})/attachments(up__ID=${incidentID},ID=${cds.utils.uuid()})`,
{ note: "This should fail" },
{ headers: { "Content-Type": "application/json" } }
)
expect.fail("Expected 404 error")
} catch (err) {
expect(err.response.status).to.equal(404)
expect(err.response.data.error.code).to.equal('AttachmentNotFound')
}
})

describe("Tests for attachments facet disable", () => {
beforeAll(async () => {
// Initialize test variables
Expand Down
20 changes: 10 additions & 10 deletions tests/non-draft-request.http
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@

### Get list of all incidents
# @name incidents
GET {{host}}/odata/v4/processor/Incidents
GET {{host}}/odata/v4/admin/Incidents
Authorization: {{auth}}

### Creating attachment (metadata request)
@incidentsID = {{incidents.response.body.value[2].ID}}
# @name createAttachment
POST {{host}}/odata/v4/processor/Incidents({{incidentsID}})/attachments
POST {{host}}/odata/v4/admin/Incidents({{incidentsID}})/attachments
Authorization: {{auth}}
Content-Type: application/json

Expand All @@ -21,40 +21,40 @@ Content-Type: application/json

### Put attachment content (content request)
@attachmentsID = {{createAttachment.response.body.ID}}
PUT {{host}}/odata/v4/processor/Incidents({{incidentsID}})/attachments(ID={{attachmentsID}})/content
PUT {{host}}/odata/v4/admin/Incidents({{incidentsID}})/attachments(ID={{attachmentsID}})/content
Authorization: {{auth}}
Content-Type: image/jpeg

< ./integration/content/sample-1.jpg

### Get newly created attachment metadata
GET {{host}}/odata/v4/processor/Incidents({{incidentsID}})/attachments(ID={{attachmentsID}})
GET {{host}}/odata/v4/admin/Incidents({{incidentsID}})/attachments(ID={{attachmentsID}})
Authorization: {{auth}}

### Fetching newly created attachment content
GET {{host}}/odata/v4/processor/Incidents({{incidentsID}})/attachments(ID={{attachmentsID}})/content
GET {{host}}/odata/v4/admin/Incidents({{incidentsID}})/attachments(ID={{attachmentsID}})/content
Authorization: {{auth}}

### Get list of attachments for a particular incident
# @name attachments
GET {{host}}/odata/v4/processor/Incidents(ID={{incidentsID}})/attachments
GET {{host}}/odata/v4/admin/Incidents(ID={{incidentsID}})/attachments
Authorization: {{auth}}

### Get attachments content
GET {{host}}/odata/v4/processor/Incidents({{incidentsID}})/attachments(ID={{attachmentsID}})/content
GET {{host}}/odata/v4/admin/Incidents({{incidentsID}})/attachments(ID={{attachmentsID}})/content
Authorization: {{auth}}

### Get attachments content with up__ID included
GET {{host}}/odata/v4/processor/Incidents({{incidentsID}})/attachments(up__ID={{incidentsID}},ID={{attachmentsID}})/content
GET {{host}}/odata/v4/admin/Incidents({{incidentsID}})/attachments(up__ID={{incidentsID}},ID={{attachmentsID}})/content
Authorization: {{auth}}

### Put attachment content (content request) with up__ID included
PUT {{host}}/odata/v4/processor/Incidents({{incidentsID}})/attachments(up__ID={{incidentsID}},ID={{attachmentsID}})/content
PUT {{host}}/odata/v4/admin/Incidents({{incidentsID}})/attachments(up__ID={{incidentsID}},ID={{attachmentsID}})/content
Authorization: {{auth}}
Content-Type: image/jpeg

< ./integration/content/sample-1.jpg

### Delete attachment
DELETE {{host}}/odata/v4/processor/Incidents({{incidentsID}})/attachments(ID={{attachmentsID}})
DELETE {{host}}/odata/v4/admin/Incidents({{incidentsID}})/attachments(ID={{attachmentsID}})
Authorization: {{auth}}
4 changes: 2 additions & 2 deletions tests/unit/validateAttachmentSize.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ describe('validateAttachmentSize', () => {
req.headers['content-length'] = '20480000000'
validateAttachmentSize(req)

expect(req.reject).toHaveBeenCalledWith(400, 'File Size limit exceeded beyond 400 MB.')
expect(req.reject).toHaveBeenCalledWith(400, 'AttachmentSizeExceeded')
})

it('should reject when content-length header is missing', () => {
validateAttachmentSize(req)

expect(req.reject).toHaveBeenCalledWith(400, 'Invalid Content Size')
expect(req.reject).toHaveBeenCalledWith(400, 'InvalidContentSize')
})
})

Loading