Skip to content

Commit 300b90f

Browse files
committed
Merge branch 'development' into feat/oauth-support
2 parents ee516f0 + 2a877cd commit 300b90f

File tree

8 files changed

+193
-73
lines changed

8 files changed

+193
-73
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77
- Handle retry the requests that were pending due to token expiration
88
- Updated Axios Version
99

10+
## [v1.19.6](https://github.com/contentstack/contentstack-management-javascript/tree/v1.19.6) (2025-03-24)
11+
- Enhancement
12+
- Added stack headers in global fields response
13+
- Added buffer upload in assets
14+
1015
## [v1.19.5](https://github.com/contentstack/contentstack-management-javascript/tree/v1.19.5) (2025-03-17)
1116
- Fix
1217
- Added AuditLog in the stack class

lib/stack/asset/index.js

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -291,11 +291,21 @@ export function createFormData (data) {
291291
if (typeof data.title === 'string') {
292292
formData.append('asset[title]', data.title)
293293
}
294-
const uploadStream = createReadStream(data.upload)
295-
if (typeof data.content_type === 'string') {
296-
formData.append('asset[upload]', uploadStream, { contentType: data.content_type })
294+
// Handle Buffer Upload
295+
if (Buffer.isBuffer(data.upload)) {
296+
formData.append('asset[upload]', data.upload, {
297+
filename: data.filename || 'uploaded_file',
298+
contentType: data.content_type || 'application/octet-stream'
299+
})
300+
} else if (typeof data.upload === 'string') { // Handle File Path Upload
301+
const uploadStream = createReadStream(data.upload)
302+
if (typeof data.content_type === 'string') {
303+
formData.append('asset[upload]', uploadStream, { contentType: data.content_type })
304+
} else {
305+
formData.append('asset[upload]', uploadStream)
306+
}
297307
} else {
298-
formData.append('asset[upload]', uploadStream)
308+
throw new Error('Invalid upload format. Must be a file path or Buffer.')
299309
}
300310
return formData
301311
}

lib/stack/globalField/index.js

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,12 @@ export function GlobalField (http, data = {}) {
5555
if (this.apiVersion) {
5656
delete this.stackHeaders.api_version
5757
}
58-
if (response.data) {
59-
return response.data
58+
const data = response.data
59+
if (data) {
60+
if (this.stackHeaders) {
61+
data.stackHeaders = this.stackHeaders
62+
}
63+
return data
6064
} else {
6165
throw error(response)
6266
}
@@ -105,8 +109,12 @@ export function GlobalField (http, data = {}) {
105109
headers: { ...cloneDeep(this.stackHeaders) }
106110
}
107111
const response = await http.put(`${this.urlPath}`, config, headers)
108-
if (response.data) {
109-
return response.data
112+
const data = response.data
113+
if (data) {
114+
if (this.stackHeaders) {
115+
data.stackHeaders = this.stackHeaders
116+
}
117+
return data
110118
} else {
111119
throw error(response)
112120
}
@@ -146,8 +154,12 @@ export function GlobalField (http, data = {}) {
146154
if (this.apiVersion) {
147155
delete this.stackHeaders.api_version
148156
}
149-
if (response.data) {
150-
return response.data
157+
const data = response.data
158+
if (data) {
159+
if (this.stackHeaders) {
160+
data.stackHeaders = this.stackHeaders
161+
}
162+
return data
151163
} else {
152164
throw error(response)
153165
}
@@ -183,8 +195,12 @@ export function GlobalField (http, data = {}) {
183195
}
184196
}
185197
const response = await http.get(this.urlPath, headers)
186-
if (response.data) {
187-
return response.data
198+
const data = response.data
199+
if (data) {
200+
if (this.stackHeaders) {
201+
data.stackHeaders = this.stackHeaders
202+
}
203+
return data
188204
} else {
189205
throw error(response)
190206
}
@@ -214,7 +230,7 @@ export function GlobalField (http, data = {}) {
214230
* client.stack().globalField().create({ global_field })
215231
* .then((globalField) => console.log(globalField))
216232
*/
217-
this.create = async (data) => {
233+
this.create = async (payload) => {
218234
try {
219235
if (this.apiVersion) {
220236
this.stackHeaders.api_version = this.apiVersion
@@ -224,11 +240,15 @@ export function GlobalField (http, data = {}) {
224240
...cloneDeep(this.stackHeaders)
225241
}
226242
}
227-
const response = await http.post(`${this.urlPath}`, data, headers)
228-
if (response.data) {
229-
return response.data
243+
const response = await http.post(`${this.urlPath}`, payload, headers)
244+
const data = response.data
245+
if (data) {
246+
if (this.stackHeaders) {
247+
data.stackHeaders = this.stackHeaders
248+
}
249+
return data
230250
} else {
231-
return error(response)
251+
throw error(response)
232252
}
233253
} catch (err) {
234254
return error(err)

sanity-report-dev11.js

Lines changed: 65 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,88 @@
1-
const dotenv = require('dotenv')
2-
const fs = require('fs')
1+
import Slack from "@slack/bolt";
2+
const { App } = Slack;
3+
import dotenv from "dotenv";
4+
import fs from "fs";
35

4-
dotenv.config()
6+
dotenv.config();
57

6-
const user1 = process.env.USER1
7-
const user2 = process.env.USER2
8-
const user3 = process.env.USER3
9-
const user4 = process.env.USER4
8+
const user1 = process.env.USER1;
9+
const user2 = process.env.USER2;
10+
const user3 = process.env.USER3;
11+
const user4 = process.env.USER4;
1012

1113
const mochawesomeJsonOutput = fs.readFileSync(
12-
'./mochawesome-report/mochawesome.json',
13-
'utf-8'
14-
)
15-
const mochawesomeReport = JSON.parse(mochawesomeJsonOutput)
14+
"./mochawesome-report/mochawesome.json",
15+
"utf-8"
16+
);
17+
const mochawesomeReport = JSON.parse(mochawesomeJsonOutput);
1618

17-
const totalTests = mochawesomeReport.stats.tests
18-
const passedTests = mochawesomeReport.stats.passes
19-
const failedTests = mochawesomeReport.stats.failures
19+
const totalTests = mochawesomeReport.stats.tests;
20+
const passedTests = mochawesomeReport.stats.passes;
21+
const failedTests = mochawesomeReport.stats.failures;
2022

21-
let durationInSeconds = Math.floor(mochawesomeReport.stats.duration / 1000)
22-
const durationInMinutes = Math.floor(durationInSeconds / 60)
23-
durationInSeconds %= 60
23+
let durationInSeconds = Math.floor(mochawesomeReport.stats.duration / 1000);
24+
const durationInMinutes = Math.floor(durationInSeconds / 60);
25+
durationInSeconds %= 60;
2426

2527
const resultMessage =
2628
passedTests === totalTests
2729
? `:white_check_mark: Success (${passedTests} / ${totalTests} Passed)`
28-
: `:x: Failure (${passedTests} / ${totalTests} Passed)`
30+
: `:x: Failure (${passedTests} / ${totalTests} Passed)`;
2931

30-
const pipelineName = process.env.GO_PIPELINE_NAME
31-
const pipelineCounter = process.env.GO_PIPELINE_COUNTER
32-
const goCdServer = process.env.GOCD_SERVER
32+
const pipelineName = process.env.GO_PIPELINE_NAME;
33+
const pipelineCounter = process.env.GO_PIPELINE_COUNTER;
34+
const goCdServer = process.env.GOCD_SERVER;
3335

34-
const reportUrl = `http://${goCdServer}/go/files/${pipelineName}/${pipelineCounter}/sanity/1/sanity/test-results/mochawesome-report/sanity-report.html`
36+
const reportUrl = `http://${goCdServer}/go/files/${pipelineName}/${pipelineCounter}/sanity/1/sanity/test-results/mochawesome-report/sanity-report.html`;
3537

36-
let tagUsers = ``
37-
if (failedTests > 0) {
38-
tagUsers = `<@${user1}> <@${user2}> <@${user3}> <@${user4}>`
39-
}
38+
let tagUsers =
39+
failedTests > 0 ? `<@${user1}> <@${user2}> <@${user3}> <@${user4}>` : "";
4040

4141
const slackMessage = {
42-
text: `Dev11, SDK-CMA Sanity
43-
*Result:* ${resultMessage}. ${durationInMinutes}m ${durationInSeconds}s
44-
*Failed Tests:* ${failedTests}
45-
<${reportUrl}|View Report>
46-
${tagUsers}`
47-
}
42+
text: `Dev11, SDK-CMA Sanity\n*Result:* ${resultMessage}. ${durationInMinutes}m ${durationInSeconds}s\n*Failed Tests:* ${failedTests}\n<${reportUrl}|View Report>\n${tagUsers}`,
43+
};
4844

49-
const slackWebhookUrl = process.env.SLACK_WEBHOOK_URL
45+
const app = new App({
46+
token: process.env.SLACK_BOT_TOKEN,
47+
signingSecret: process.env.SLACK_SIGNING_SECRET,
48+
});
5049

5150
const sendSlackMessage = async (message) => {
52-
const payload = {
53-
text: message
54-
}
55-
5651
try {
57-
const response = await fetch(slackWebhookUrl, {
58-
method: 'POST',
59-
headers: {
60-
'Content-Type': 'application/json'
61-
},
62-
body: JSON.stringify(payload)
63-
})
64-
65-
if (!response.ok) {
66-
throw new Error(`Error sending message to Slack: ${response.statusText}`)
52+
const result = await app.client.chat.postMessage({
53+
token: process.env.SLACK_BOT_TOKEN,
54+
channel: process.env.SLACK_CHANNEL2,
55+
text: message,
56+
});
57+
58+
if (failedTests > 0) {
59+
await sendFailureDetails(result.ts);
6760
}
61+
} catch (error) {
62+
console.error("Error sending Slack message:", error);
63+
}
64+
};
6865

69-
console.log('Message sent to Slack successfully')
66+
const sendFailureDetails = async (threadTs) => {
67+
const failedSuites = mochawesomeReport.results
68+
.flatMap((result) => result.suites)
69+
.filter((suite) => suite.failures.length > 0);
70+
71+
let failureDetails = "*Failed Test Modules:*\n";
72+
for (const suite of failedSuites) {
73+
failureDetails += `- *${suite.title}*: ${suite.failures.length} failed\n`;
74+
}
75+
76+
try {
77+
await app.client.chat.postMessage({
78+
token: process.env.SLACK_BOT_TOKEN,
79+
channel: process.env.SLACK_CHANNEL,
80+
text: failureDetails,
81+
thread_ts: threadTs,
82+
});
7083
} catch (error) {
71-
console.error('Error:', error)
84+
console.error("Error sending failure details:", error);
7285
}
73-
}
74-
sendSlackMessage(slackMessage.text)
86+
};
87+
88+
sendSlackMessage(slackMessage.text);

test/sanity-check/api/asset-test.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import fs from 'fs'
12
import path from 'path'
23
import { expect } from 'chai'
34
import { describe, it, setup } from 'mocha'
@@ -39,6 +40,31 @@ describe('Assets api Test', () => {
3940
.catch(done)
4041
})
4142

43+
it('should upload asset from buffer', (done) => {
44+
const filePath = path.join(__dirname, '../mock/customUpload.html')
45+
const fileBuffer = fs.readFileSync(filePath) // Read file into Buffer
46+
const asset = {
47+
upload: fileBuffer, // Buffer upload
48+
filename: 'customUpload.html', // Ensure filename is provided
49+
content_type: 'text/html', // Set content type
50+
title: 'buffer-asset',
51+
description: 'Buffer Asset Desc',
52+
tags: ['Buffer']
53+
}
54+
makeAsset().create(asset)
55+
.then((asset) => {
56+
jsonWrite(asset, 'bufferAsset.json')
57+
expect(asset.uid).to.be.not.equal(null)
58+
expect(asset.url).to.be.not.equal(null)
59+
expect(asset.filename).to.be.equal('customUpload.html')
60+
expect(asset.title).to.be.equal('buffer-asset')
61+
expect(asset.description).to.be.equal('Buffer Asset Desc')
62+
expect(asset.content_type).to.be.equal('text/html')
63+
done()
64+
})
65+
.catch(done)
66+
})
67+
4268
it('should download asset from URL.', done => {
4369
makeAsset().download({ url: assetURL, responseType: 'stream' })
4470
.then((response) => {

test/unit/asset-test.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import fs from 'fs'
12
import path from 'path'
23
import Axios from 'axios'
34
import { expect } from 'chai'
@@ -171,6 +172,49 @@ describe('Contentstack Asset test', () => {
171172
.catch(done)
172173
})
173174

175+
it('should upload asset from buffer', (done) => {
176+
const mock = new MockAdapter(Axios)
177+
mock.onPost('/assets').reply(200, {
178+
asset: {
179+
uid: 'mock-uid',
180+
url: '/assets',
181+
filename: 'customUpload.html',
182+
title: 'buffer-asset',
183+
description: 'Buffer Asset Desc',
184+
content_type: 'text/html',
185+
tags: ['Buffer'],
186+
parent_uid: 'UID'
187+
}
188+
})
189+
const filePath = path.join(__dirname, '../api/mock/customUpload.html')
190+
const fileBuffer = fs.readFileSync(filePath)
191+
const assetUpload = {
192+
upload: fileBuffer, // Buffer upload
193+
filename: 'customUpload.html', // Filename to identify the file
194+
content_type: 'text/html', // MIME type
195+
title: 'buffer-asset',
196+
description: 'Buffer Asset Desc',
197+
tags: ['Buffer'],
198+
parent_uid: 'UID'
199+
}
200+
const form = createFormData(assetUpload)() // Create FormData for Buffer upload
201+
const boundary = form.getBoundary()
202+
expect(boundary).to.be.equal(form.getBoundary())
203+
expect(boundary.length).to.be.greaterThan(30)
204+
makeAsset()
205+
.create(assetUpload)
206+
.then((asset) => {
207+
expect(asset.uid).to.be.equal('mock-uid')
208+
expect(asset.filename).to.be.equal('customUpload.html')
209+
expect(asset.title).to.be.equal('buffer-asset')
210+
expect(asset.description).to.be.equal('Buffer Asset Desc')
211+
expect(asset.content_type).to.be.equal('text/html')
212+
expect(asset.tags).to.include('Buffer')
213+
done()
214+
})
215+
.catch(done)
216+
})
217+
174218
it('Asset replace test', done => {
175219
var mock = new MockAdapter(Axios)
176220
mock.onPut('/assets/UID').reply(200, {

types/stack/globalField/index.d.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ export interface GlobalField extends SystemFields, SystemFunction<GlobalField> {
88

99
export interface GlobalFields extends Queryable<GlobalField, {global_field: GlobalFieldData}> {
1010
import(data: {global_field: string}, params?: any): Promise<GlobalField>
11-
(globalFieldUidOrOptions: string | { api_version?: string }, options?: { api_version?: string }): GlobalField;
1211
}
1312

1413
export interface GlobalFieldData extends AnyProperty {

types/stack/index.d.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,12 @@ export interface Stack extends SystemFields {
6161
contentType(): ContentTypes
6262
contentType(uid: string): ContentType
6363

64-
globalField(): GlobalFields
65-
globalField({}): GlobalFields
66-
globalField(uid: string): GlobalField
67-
globalField(uid: string, option: object): GlobalField
64+
globalField(): GlobalFields;
65+
globalField(uid: string, option?: object): GlobalField;
66+
globalField(options: { api_version: string }): GlobalFields;
67+
globalField(uidOrOptions?: string | { api_version: string }, option?: object): GlobalFields | GlobalField;
68+
69+
6870

6971
asset(): Assets
7072
asset(uid: string): Asset

0 commit comments

Comments
 (0)