Skip to content

Commit e776cdb

Browse files
committed
feat: add asset scan status tests and bulk publish validation for API version 3.2
1 parent 391fcc0 commit e776cdb

4 files changed

Lines changed: 211 additions & 0 deletions

File tree

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

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -981,4 +981,84 @@ describe('Asset API Tests', () => {
981981
}
982982
})
983983
})
984+
985+
// ==========================================================================
986+
// ASSET SCAN STATUS
987+
// ==========================================================================
988+
989+
describe('Asset Scan Status', () => {
990+
let assetUid
991+
992+
before(async function () {
993+
this.timeout(30000)
994+
// Reuse an asset created by the Asset Upload block to avoid an extra upload.
995+
// Fall back to creating a fresh one only if that block didn't succeed.
996+
if (testData.assets.image && testData.assets.image.uid) {
997+
assetUid = testData.assets.image.uid
998+
return
999+
}
1000+
try {
1001+
const asset = await stack.asset().create({
1002+
upload: assetPath,
1003+
title: `Scan Status Test Asset ${Date.now()}`,
1004+
description: 'Asset for scan status testing'
1005+
})
1006+
assetUid = asset.uid
1007+
} catch (err) {
1008+
// Asset creation failed — individual tests will skip themselves.
1009+
}
1010+
})
1011+
1012+
it('should accept include_asset_scan_status param on single asset fetch', async function () {
1013+
if (!assetUid) return this.skip()
1014+
const asset = await stack.asset(assetUid).fetch({ include_asset_scan_status: true })
1015+
1016+
expect(asset).to.be.an('object')
1017+
expect(asset.uid).to.equal(assetUid)
1018+
// _asset_scan_status is opt-in. When scanning is enabled: pending | clean | quarantined.
1019+
// When scanning is not enabled for the stack, the API returns 'not_scanned'.
1020+
if ('_asset_scan_status' in asset) {
1021+
expect(asset._asset_scan_status).to.be.a('string')
1022+
expect(['pending', 'clean', 'quarantined', 'not_scanned']).to.include(asset._asset_scan_status)
1023+
}
1024+
})
1025+
1026+
it('should accept include_asset_scan_status param on asset list query', async function () {
1027+
const response = await stack.asset().query({ include_asset_scan_status: true }).find()
1028+
1029+
expect(response).to.be.an('object')
1030+
expect(response.items).to.be.an('array')
1031+
// pending | clean | quarantined when scanning is enabled; not_scanned otherwise.
1032+
if (response.items.length > 0 && '_asset_scan_status' in response.items[0]) {
1033+
expect(response.items[0]._asset_scan_status).to.be.a('string')
1034+
expect(['pending', 'clean', 'quarantined', 'not_scanned']).to.include(response.items[0]._asset_scan_status)
1035+
}
1036+
})
1037+
1038+
it('should not return _asset_scan_status when param is omitted', async function () {
1039+
if (!assetUid) return this.skip()
1040+
const asset = await stack.asset(assetUid).fetch()
1041+
1042+
expect(asset).to.be.an('object')
1043+
expect(asset).to.not.have.property('_asset_scan_status')
1044+
})
1045+
1046+
it('should accept include_asset_scan_status param on asset upload', async function () {
1047+
this.timeout(30000)
1048+
const asset = await stack.asset().create(
1049+
{
1050+
upload: assetPath,
1051+
title: `Scan Status Upload Test ${Date.now()}`
1052+
},
1053+
{ include_asset_scan_status: true }
1054+
)
1055+
1056+
expect(asset).to.be.an('object')
1057+
expect(asset.uid).to.be.a('string')
1058+
// pending when scanning is enabled; not_scanned when scanning is not enabled for the stack.
1059+
if ('_asset_scan_status' in asset) {
1060+
expect(['pending', 'not_scanned']).to.include(asset._asset_scan_status)
1061+
}
1062+
})
1063+
})
9841064
})

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,30 @@ describe('BulkOperation api test', () => {
544544
.catch(done)
545545
})
546546

547+
// Asset Scanning: bulk publish must send api_version: 3.2 so the CDA runs scan validation.
548+
// Without this header, quarantined assets incorrectly appear as published in the UI.
549+
describe('Asset Scanning — Bulk Publish with api_version: 3.2', function () {
550+
it('should bulk publish assets with api_version: 3.2 header for CDA scan validation', async function () {
551+
this.timeout(30000)
552+
const assets = assetsWithValidUids()
553+
if (assets.length === 0) {
554+
return this.skip()
555+
}
556+
const publishDetails = {
557+
assets,
558+
locales: ['en-us'],
559+
environments: [envName]
560+
}
561+
562+
const response = await doBulkOperation().publish({ details: publishDetails, api_version: '3.2' })
563+
564+
// Bulk publish always returns a job_id regardless of individual asset scan status.
565+
// Actual scan failures surface asynchronously in the Publish Queue UI.
566+
expect(response.notice).to.be.a('string')
567+
expect(response.job_id).to.be.a('string')
568+
})
569+
})
570+
547571
// DX-4430 regression: SDK was masking real API errors (401+error_code 161/294) with
548572
// generic "Session timed out, please login to proceed" / "Unable to refresh token".
549573
// Fix: NON_AUTH_401_ERROR_CODES={161,294} bypass token refresh and surface original error.

test/unit/asset-test.js

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,92 @@ describe('Contentstack Asset test', () => {
463463
})
464464
.catch(done)
465465
})
466+
467+
// Asset Scanning tests
468+
it('Asset fetch sends include_asset_scan_status as query param and returns _asset_scan_status', done => {
469+
var mock = new MockAdapter(Axios)
470+
mock.onGet('/assets/UID').reply((config) => {
471+
expect(config.params.include_asset_scan_status).to.equal(true)
472+
return [200, {
473+
asset: {
474+
...assetMock,
475+
_asset_scan_status: 'clean'
476+
}
477+
}]
478+
})
479+
makeAsset({
480+
asset: { ...systemUidMock },
481+
stackHeaders: stackHeadersMock
482+
})
483+
.fetch({ include_asset_scan_status: true })
484+
.then((asset) => {
485+
checkAsset(asset)
486+
expect(asset._asset_scan_status).to.equal('clean')
487+
done()
488+
})
489+
.catch(done)
490+
})
491+
492+
it('Asset query sends include_asset_scan_status as query param and returns _asset_scan_status', done => {
493+
var mock = new MockAdapter(Axios)
494+
mock.onGet('/assets').reply((config) => {
495+
expect(config.params.include_asset_scan_status).to.equal(true)
496+
return [200, {
497+
assets: [{
498+
...assetMock,
499+
_asset_scan_status: 'clean'
500+
}]
501+
}]
502+
})
503+
makeAsset()
504+
.query({ include_asset_scan_status: true })
505+
.find()
506+
.then((collection) => {
507+
expect(collection.items[0]._asset_scan_status).to.equal('clean')
508+
done()
509+
})
510+
.catch(done)
511+
})
512+
513+
it('Asset create sends include_asset_scan_status as query param and returns pending scan status', done => {
514+
var mock = new MockAdapter(Axios)
515+
mock.onPost('/assets').reply((config) => {
516+
expect(config.params.include_asset_scan_status).to.equal(true)
517+
return [200, {
518+
asset: {
519+
...assetMock,
520+
_asset_scan_status: 'pending'
521+
}
522+
}]
523+
})
524+
makeAsset()
525+
.create(
526+
{ upload: path.join(__dirname, '../sanity-check/mock/customUpload.html') },
527+
{ include_asset_scan_status: true }
528+
)
529+
.then((asset) => {
530+
expect(asset._asset_scan_status).to.equal('pending')
531+
done()
532+
})
533+
.catch(done)
534+
})
535+
536+
it('Asset fetch without include_asset_scan_status does not return _asset_scan_status', done => {
537+
var mock = new MockAdapter(Axios)
538+
mock.onGet('/assets/UID').reply(200, {
539+
asset: { ...assetMock }
540+
})
541+
makeAsset({
542+
asset: { ...systemUidMock },
543+
stackHeaders: stackHeadersMock
544+
})
545+
.fetch()
546+
.then((asset) => {
547+
expect(asset._asset_scan_status).to.equal(undefined)
548+
done()
549+
})
550+
.catch(done)
551+
})
466552
})
467553

468554
function makeAsset (data) {

test/unit/bulkOperation-test.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,27 @@ describe('Contentstack BulkOperation test', () => {
107107
expect(response.job_id).to.not.equal(undefined)
108108
})
109109

110+
it('should send api_version 3.2 header when bulk publishing assets for scan validation', async () => {
111+
const publishDetails = {
112+
assets: [{ uid: 'asset_uid', version: 1, locale: 'en-us' }],
113+
locales: ['en-us'],
114+
environments: ['development']
115+
}
116+
117+
const mock = new MockAdapter(Axios)
118+
mock.onPost('/bulk/publish').reply((config) => {
119+
expect(config.headers.api_version).to.equal('3.2')
120+
return [200, {
121+
notice: 'Your bulk publish request is in progress. Please check publish queue for more details.',
122+
job_id: 'job_id'
123+
}]
124+
})
125+
126+
const response = await makeBulkOperation().publish({ details: publishDetails, api_version: '3.2' })
127+
expect(response.notice).to.include('bulk publish')
128+
expect(response.job_id).to.not.equal(undefined)
129+
})
130+
110131
it('should unpublish items in bulk', async () => {
111132
const unpublishDetails = {
112133
entries: [

0 commit comments

Comments
 (0)