diff options
author | jenkins-bot <jenkins-bot@gerrit.wikimedia.org> | 2025-01-16 21:03:14 +0000 |
---|---|---|
committer | Gerrit Code Review <gerrit@wikimedia.org> | 2025-01-16 21:03:14 +0000 |
commit | 2e479834024fcb38df666266ee6a4d5ac998e9ca (patch) | |
tree | 14e991b74352961be52cdca30a55946625a037be /tests/api-testing/REST | |
parent | 8fd9549784bc341adbecd94684a5ccf4c9a3a6cd (diff) | |
parent | 8dfcccc720a685448db47d3c202299c1100cb0c1 (diff) | |
download | mediawikicore-2e479834024fcb38df666266ee6a4d5ac998e9ca.tar.gz mediawikicore-2e479834024fcb38df666266ee6a4d5ac998e9ca.zip |
Merge "REST: Content/v1: Validate responses against response schemas in tests"
Diffstat (limited to 'tests/api-testing/REST')
-rw-r--r-- | tests/api-testing/REST/CreationLegacy.js | 2 | ||||
-rw-r--r-- | tests/api-testing/REST/PageLegacy.js | 2 | ||||
-rw-r--r-- | tests/api-testing/REST/UpdateLegacy.js | 2 | ||||
-rw-r--r-- | tests/api-testing/REST/content.v1/Creation.js | 100 | ||||
-rw-r--r-- | tests/api-testing/REST/content.v1/Page.js | 142 | ||||
-rw-r--r-- | tests/api-testing/REST/content.v1/Revision.js | 18 | ||||
-rw-r--r-- | tests/api-testing/REST/content.v1/Update.js | 141 |
7 files changed, 285 insertions, 122 deletions
diff --git a/tests/api-testing/REST/CreationLegacy.js b/tests/api-testing/REST/CreationLegacy.js index 19dab5e079c7..5f176f50fddb 100644 --- a/tests/api-testing/REST/CreationLegacy.js +++ b/tests/api-testing/REST/CreationLegacy.js @@ -5,6 +5,6 @@ describe( 'legacy POST /page', () => { // Doing this twice protects against changes in test execution order const testsFile = __dirname + '/content.v1/Creation.js'; delete require.cache[ testsFile ]; - require( testsFile ).init( 'rest.php/v1' ); + require( testsFile ).init( '/v1', '/-' ); delete require.cache[ testsFile ]; } ); diff --git a/tests/api-testing/REST/PageLegacy.js b/tests/api-testing/REST/PageLegacy.js index 2b3cdae3f4f1..3c2c805e8d75 100644 --- a/tests/api-testing/REST/PageLegacy.js +++ b/tests/api-testing/REST/PageLegacy.js @@ -5,6 +5,6 @@ describe( 'legacy Page Source', () => { // Doing this twice protects against changes in test execution order const testsFile = __dirname + '/content.v1/Page.js'; delete require.cache[ testsFile ]; - require( testsFile ).init( 'rest.php/v1' ); + require( testsFile ).init( '/v1', '/-' ); delete require.cache[ testsFile ]; } ); diff --git a/tests/api-testing/REST/UpdateLegacy.js b/tests/api-testing/REST/UpdateLegacy.js index e9e0e5b14920..5e49b2d8f5f4 100644 --- a/tests/api-testing/REST/UpdateLegacy.js +++ b/tests/api-testing/REST/UpdateLegacy.js @@ -5,6 +5,6 @@ describe( 'legacy PUT /page/{title}', () => { // Doing this twice protects against changes in test execution order const testsFile = __dirname + '/content.v1/Update.js'; delete require.cache[ testsFile ]; - require( testsFile ).init( 'rest.php/v1' ); + require( testsFile ).init( '/v1', '/-' ); delete require.cache[ testsFile ]; } ); diff --git a/tests/api-testing/REST/content.v1/Creation.js b/tests/api-testing/REST/content.v1/Creation.js index e592ed107e3d..e609056198fc 100644 --- a/tests/api-testing/REST/content.v1/Creation.js +++ b/tests/api-testing/REST/content.v1/Creation.js @@ -3,19 +3,33 @@ const { action, assert, REST, utils } = require( 'api-testing' ); const supertest = require( 'supertest' ); -let pathPrefix = 'rest.php/content/v1'; +const chai = require( 'chai' ); +const expect = chai.expect; + +const chaiResponseValidator = require( 'chai-openapi-response-validator' ).default; + +let pathPrefix = '/content/v1'; +let specModule = '/content/v1'; describe( 'POST /page', () => { - let client, mindy, anon, anonToken; + let client, mindy, anon, anonToken, openApiSpec; beforeEach( async () => { // Reset the client and token before each test // In a temp account context, making an anonymous edit generates an account // so we want to reset state after each edit mindy = await action.mindy(); - client = new REST( pathPrefix ); + client = new REST( 'rest.php' ); anon = await action.getAnon(); anonToken = await anon.token(); + + const specPath = '/specs/v0/module' + specModule; + const { status, text } = await client.get( specPath ); + assert.deepEqual( status, 200 ); + + openApiSpec = JSON.parse( text ); + chai.use( chaiResponseValidator( openApiSpec ) ); + } ); const checkEditResponse = function ( title, reqBody, body ) { @@ -61,10 +75,12 @@ describe( 'POST /page', () => { title }; - const { status: editStatus, body: editBody, header } = - await client.post( '/page', reqBody ); + const newPage = await client.post( `${ pathPrefix }/page`, reqBody ); + const { status: editStatus, body: editBody, header } = newPage; assert.equal( editStatus, 201 ); assert.match( header[ 'content-type' ], /^application\/json/ ); + // eslint-disable-next-line no-unused-expressions + expect( newPage ).to.satisfyApiSpec; assert.nestedProperty( header, 'location' ); const location = header.location; @@ -79,11 +95,15 @@ describe( 'POST /page', () => { checkSourceResponse( title, reqBody, redirBody ); // construct request to fetch content - const { status: sourceStatus, body: sourceBody, header: sourceHeader } = - await client.get( `/page/${ normalizedTitle }` ); + const res = await client.get( `${ pathPrefix }/page/${ normalizedTitle }` ); + const { status: sourceStatus, body: sourceBody, header: sourceHeader } = res; assert.equal( sourceStatus, 200 ); assert.match( sourceHeader[ 'content-type' ], /^application\/json/ ); checkSourceResponse( title, reqBody, sourceBody ); + + // eslint-disable-next-line no-unused-expressions + expect( res ).to.satisfyApiSpec; + } ); it( 'should create a page with specified model', async () => { @@ -100,17 +120,24 @@ describe( 'POST /page', () => { title }; - const { status: editStatus, body: editBody, header: editHeader } = - await client.post( '/page', reqBody ); + const newPage = await client.post( `${ pathPrefix }/page`, reqBody ); + const { status: editStatus, body: editBody, header: editHeader } = newPage; + assert.equal( editStatus, 201 ); assert.match( editHeader[ 'content-type' ], /^application\/json/ ); + // eslint-disable-next-line no-unused-expressions + expect( newPage ).to.satisfyApiSpec; checkEditResponse( title, reqBody, editBody ); - const { status: sourceStatus, body: sourceBody, header: sourceHeader } = - await client.get( `/page/${ normalizedTitle }` ); + const res = await client.get( `${ pathPrefix }/page/${ normalizedTitle }` ); + const { status: sourceStatus, body: sourceBody, header: sourceHeader } = res; assert.equal( sourceStatus, 200 ); assert.match( sourceHeader[ 'content-type' ], /^application\/json/ ); checkSourceResponse( title, reqBody, sourceBody ); + + // eslint-disable-next-line no-unused-expressions + expect( res ).to.satisfyApiSpec; + } ); } ); @@ -130,12 +157,14 @@ describe( 'POST /page', () => { const incompleteBody = { ...reqBody }; delete incompleteBody[ missingPropName ]; - const { status: editStatus, body: editBody, header: editHeader } = - await client.post( '/page', incompleteBody ); + const newPage = await client.post( `${ pathPrefix }/page`, incompleteBody ); + const { status: editStatus, body: editBody, header: editHeader } = newPage; assert.equal( editStatus, 400 ); assert.match( editHeader[ 'content-type' ], /^application\/json/ ); assert.nestedProperty( editBody, 'messageTranslations' ); + // eslint-disable-next-line no-unused-expressions + expect( newPage ).to.satisfyApiSpec; } ); } ); @@ -148,12 +177,15 @@ describe( 'POST /page', () => { title }; - const { status: editStatus, body: editBody, header: editHeader } = - await client.post( '/page', reqBody ); + const newPage = await client.post( `${ pathPrefix }/page`, reqBody ); + const { status: editStatus, body: editBody, header: editHeader } = newPage; assert.equal( editStatus, 403 ); assert.match( editHeader[ 'content-type' ], /^application\/json/ ); assert.nestedProperty( editBody, 'messageTranslations' ); + // eslint-disable-next-line no-unused-expressions + expect( newPage ).to.satisfyApiSpec; + } ); it( 'should fail if a bad token is given', async () => { @@ -165,12 +197,15 @@ describe( 'POST /page', () => { title }; - const { status: editStatus, body: editBody, header: editHeader } = - await client.post( '/page', reqBody ); + const newPage = await client.post( `${ pathPrefix }/page`, reqBody ); + const { status: editStatus, body: editBody, header: editHeader } = newPage; assert.equal( editStatus, 403 ); assert.match( editHeader[ 'content-type' ], /^application\/json/ ); assert.nestedProperty( editBody, 'messageTranslations' ); + // eslint-disable-next-line no-unused-expressions + expect( newPage ).to.satisfyApiSpec; + } ); it( 'should fail if a bad content model is given', async () => { @@ -183,12 +218,15 @@ describe( 'POST /page', () => { content_model: 'THIS DOES NOT EXIST!', title }; - const { status: editStatus, body: editBody, header: editHeader } = - await client.post( '/page', reqBody ); + const newPage = await client.post( `${ pathPrefix }/page`, reqBody ); + const { status: editStatus, body: editBody, header: editHeader } = newPage; assert.equal( editStatus, 400 ); assert.match( editHeader[ 'content-type' ], /^application\/json/ ); assert.nestedProperty( editBody, 'messageTranslations' ); + // eslint-disable-next-line no-unused-expressions + expect( newPage ).to.satisfyApiSpec; + } ); it( 'should fail if a bad title is given', async () => { @@ -200,12 +238,15 @@ describe( 'POST /page', () => { comment: 'tästing', title }; - const { status: editStatus, body: editBody, header: editHeader } = - await client.post( '/page', reqBody ); + const newPage = await client.post( `${ pathPrefix }/page`, reqBody ); + const { status: editStatus, body: editBody, header: editHeader } = newPage; assert.equal( editStatus, 400 ); assert.match( editHeader[ 'content-type' ], /^application\/json/ ); assert.nestedProperty( editBody, 'messageTranslations' ); + // eslint-disable-next-line no-unused-expressions + expect( newPage ).to.satisfyApiSpec; + } ); } ); @@ -222,12 +263,15 @@ describe( 'POST /page', () => { comment: 'tästing', title }; - const { status: editStatus, body: editBody, header: editHeader } = - await client.post( '/page', reqBody ); + const newPage = await client.post( `${ pathPrefix }/page`, reqBody ); + const { status: editStatus, body: editBody, header: editHeader } = newPage; assert.equal( editStatus, 409 ); assert.match( editHeader[ 'content-type' ], /^application\/json/ ); assert.nestedProperty( editBody, 'messageTranslations' ); + // eslint-disable-next-line no-unused-expressions + expect( newPage ).to.satisfyApiSpec; + } ); } ); @@ -248,19 +292,23 @@ describe( 'POST /page', () => { comment: 'tästing', title }; - const { status: editStatus, body: editBody, header: editHeader } = - await client.post( '/page', reqBody ); + const newPage = await client.post( `${ pathPrefix }/page`, reqBody ); + const { status: editStatus, body: editBody, header: editHeader } = newPage; assert.equal( editStatus, 403 ); assert.match( editHeader[ 'content-type' ], /^application\/json/ ); assert.nestedProperty( editBody, 'messageTranslations' ); + // eslint-disable-next-line no-unused-expressions + expect( newPage ).to.satisfyApiSpec; + } ); } ); } ); // eslint-disable-next-line mocha/no-exports -exports.init = function ( pp ) { +exports.init = function ( pp, sm ) { // Allow testing both legacy and module paths using the same tests pathPrefix = pp; + specModule = sm; }; diff --git a/tests/api-testing/REST/content.v1/Page.js b/tests/api-testing/REST/content.v1/Page.js index 58b82ab9304d..087b96b36ad0 100644 --- a/tests/api-testing/REST/content.v1/Page.js +++ b/tests/api-testing/REST/content.v1/Page.js @@ -2,8 +2,13 @@ const { action, assert, REST, utils } = require( 'api-testing' ); const url = require( 'url' ); +const chai = require( 'chai' ); +const expect = chai.expect; -let pathPrefix = 'rest.php/content/v1'; +const chaiResponseValidator = require( 'chai-openapi-response-validator' ).default; + +let pathPrefix = '/content/v1'; +let specModule = '/content/v1'; // Parse a URL-ref, which may or may not contain a protocol and host. // WHATWG URL currently doesn't support partial URLs, see https://github.com/whatwg/url/issues/531 @@ -44,10 +49,11 @@ describe( 'Page Source', () => { let client; let mindy; + let openApiSpec; const baseEditText = "''Edit 1'' and '''Edit 2'''"; before( async () => { - client = new REST( pathPrefix ); + client = new REST( 'rest.php' ); mindy = await action.mindy(); await mindy.edit( page, { text: baseEditText } ); @@ -61,12 +67,19 @@ describe( 'Page Source', () => { to: redirectedPage, token }, true ); + + const specPath = '/specs/v0/module' + specModule; + const { status, text } = await client.get( specPath ); + assert.deepEqual( status, 200 ); + + openApiSpec = JSON.parse( text ); + chai.use( chaiResponseValidator( openApiSpec ) ); } ); describe( 'GET /page/{title}', () => { it( 'Title normalization should return permanent redirect (301)', async () => { const redirectDbKey = utils.dbkey( redirectPage ); - const { status, text, headers } = await client.get( `/page/${ redirectPage }`, { flavor: 'edit' } ); + const { status, text, headers } = await client.get( `${ pathPrefix }/page/${ redirectPage }`, { flavor: 'edit' } ); const { host, search, pathname } = parseURL( headers.location ); assert.include( search, 'flavor=edit' ); assert.deepEqual( host, '' ); @@ -77,15 +90,18 @@ describe( 'Page Source', () => { it( 'When a wiki redirect exists, it should be present in the body response', async () => { const redirectPageDbkey = utils.dbkey( redirectPage ); const redirectedPageDbKey = utils.dbkey( redirectedPage ); - const { status, body: { redirect_target }, text, headers } = - await client.get( `/page/${ redirectPageDbkey }` ); + const res = await client.get( `${ pathPrefix }/page/${ redirectPageDbkey }` ); + const { status, body: { redirect_target }, text, headers } = res; assert.deepEqual( status, 200, text ); assert.match( headers[ 'content-type' ], /^application\/json/ ); assert.match( redirect_target, new RegExp( `/page/${ encodeURIComponent( redirectedPageDbKey ) }$` ) ); + // eslint-disable-next-line no-unused-expressions + expect( res ).to.satisfyApiSpec; } ); it( 'Should successfully return page source and metadata for Wikitext page', async () => { - const { status, body, text, headers } = await client.get( `/page/${ page }` ); + const res = await client.get( `${ pathPrefix }/page/${ page }` ); + const { status, body, text, headers } = res; assert.deepEqual( status, 200, text ); assert.match( headers[ 'content-type' ], /^application\/json/ ); assert.match( headers.vary, /\bx-restbase-compat\b/ ); @@ -94,29 +110,31 @@ describe( 'Page Source', () => { assert.nestedPropertyVal( body, 'title', pageWithSpaces ); assert.nestedPropertyVal( body, 'key', utils.dbkey( page ) ); assert.nestedPropertyVal( body, 'source', baseEditText ); + // eslint-disable-next-line no-unused-expressions + expect( res ).to.satisfyApiSpec; } ); it( 'Should return 404 error for non-existent page', async () => { const dummyPageTitle = utils.title( 'DummyPage_' ); - const { status } = await client.get( `/page/${ dummyPageTitle }` ); + const { status } = await client.get( `${ pathPrefix }/page/${ dummyPageTitle }` ); assert.deepEqual( status, 404 ); } ); it( 'Should return 404 error for invalid titles', async () => { const badTitle = '::X::'; - const { status } = await client.get( `/page/${ badTitle }` ); + const { status } = await client.get( `${ pathPrefix }/page/${ badTitle }` ); assert.deepEqual( status, 404 ); } ); it( 'Should return 404 error for special pages', async () => { const badTitle = 'Special:Blankpage'; - const { status } = await client.get( `/page/${ badTitle }` ); + const { status } = await client.get( `${ pathPrefix }/page/${ badTitle }` ); assert.deepEqual( status, 404 ); } ); it( 'Should have appropriate response headers', async () => { - const preEditResponse = await client.get( `/page/${ page }` ); + const preEditResponse = await client.get( `${ pathPrefix }/page/${ page }` ); const preEditDate = new Date( preEditResponse.body.latest.timestamp ); const preEditEtag = preEditResponse.headers.etag; await mindy.edit( page, { text: "'''Edit 3'''" } ); - const postEditResponse = await client.get( `/page/${ page }` ); + const postEditResponse = await client.get( `${ pathPrefix }/page/${ page }` ); const postEditDate = new Date( postEditResponse.body.latest.timestamp ); const postEditHeaders = postEditResponse.headers; const postEditEtag = postEditResponse.headers.etag; @@ -133,7 +151,7 @@ describe( 'Page Source', () => { describe( 'GET /page/{title}/bare', () => { it( 'Title normalization should return permanent redirect (301)', async () => { - const { status, text, headers } = await client.get( `/page/${ redirectPage }/bare`, { flavor: 'edit' } ); + const { status, text, headers } = await client.get( `${ pathPrefix }/page/${ redirectPage }/bare`, { flavor: 'edit' } ); const { search } = parseURL( headers.location ); assert.include( search, 'flavor=edit' ); assert.deepEqual( status, 301, text ); @@ -142,15 +160,18 @@ describe( 'Page Source', () => { it( 'When a wiki redirect exists, it should be present in the body response', async () => { const redirectPageDbkey = utils.dbkey( redirectPage ); const redirectedPageDbKey = utils.dbkey( redirectedPage ); - const { status, body: { redirect_target }, text, headers } = - await client.get( `/page/${ redirectPageDbkey }/bare` ); + const res = await client.get( `${ pathPrefix }/page/${ redirectPageDbkey }/bare` ); + const { status, body: { redirect_target }, text, headers } = res; assert.deepEqual( status, 200, text ); assert.match( headers[ 'content-type' ], /^application\/json/ ); assert.match( redirect_target, new RegExp( `/page/${ encodeURIComponent( redirectedPageDbKey ) }/bare$` ) ); + // eslint-disable-next-line no-unused-expressions + expect( res ).to.satisfyApiSpec; } ); it( 'Should successfully return page bare', async () => { - const { status, body, text, headers } = await client.get( `/page/${ page }/bare` ); + const res = await client.get( `${ pathPrefix }/page/${ page }/bare` ); + const { status, body, text, headers } = res; assert.deepEqual( status, 200, text ); assert.match( headers[ 'content-type' ], /^application\/json/ ); assert.containsAllKeys( body, [ 'latest', 'id', 'key', 'license', 'title', 'content_model', 'html_url' ] ); @@ -158,19 +179,21 @@ describe( 'Page Source', () => { assert.nestedPropertyVal( body, 'title', pageWithSpaces ); assert.nestedPropertyVal( body, 'key', utils.dbkey( page ) ); assert.match( body.html_url, new RegExp( `/page/${ encodeURIComponent( pageWithSpaces ) }/html$` ) ); + // eslint-disable-next-line no-unused-expressions + expect( res ).to.satisfyApiSpec; } ); it( 'Should return 404 error for non-existent page, even if a variant exists', async () => { const agepayDbkey = utils.dbkey( agepay ); - const { status } = await client.get( `/page/${ agepayDbkey }/bare` ); + const { status } = await client.get( `${ pathPrefix }/page/${ agepayDbkey }/bare` ); assert.deepEqual( status, 404 ); } ); it( 'Should have appropriate response headers', async () => { - const preEditResponse = await client.get( `/page/${ page }/bare` ); + const preEditResponse = await client.get( `${ pathPrefix }/page/${ page }/bare` ); const preEditDate = new Date( preEditResponse.body.latest.timestamp ); const preEditEtag = preEditResponse.headers.etag; await mindy.edit( page, { text: "'''Edit 4'''" } ); - const postEditResponse = await client.get( `/page/${ page }/bare` ); + const postEditResponse = await client.get( `${ pathPrefix }/page/${ page }/bare` ); const postEditDate = new Date( postEditResponse.body.latest.timestamp ); const postEditHeaders = postEditResponse.headers; const postEditEtag = postEditResponse.headers.etag; @@ -188,9 +211,8 @@ describe( 'Page Source', () => { describe( 'GET /page/{title}/bare with x-restbase-compat', () => { it( 'Should successfully return restbase-compatible revision meta-data', async () => { const { status, body, text, headers } = await client - .get( `/page/${ page }/bare` ) + .get( `${ pathPrefix }/page/${ page }/bare` ) .set( 'x-restbase-compat', 'true' ); - assert.deepEqual( status, 200, text ); assert.match( headers[ 'content-type' ], /^application\/json/ ); assert.match( headers.vary, /\bx-restbase-compat\b/ ); @@ -205,7 +227,7 @@ describe( 'Page Source', () => { it( 'Should successfully return restbase-compatible errors', async () => { const dummyPageTitle = utils.title( 'DummyPage_' ); const { status, body, text, headers } = await client - .get( `/page/${ dummyPageTitle }/bare` ) + .get( `${ pathPrefix }/page/${ dummyPageTitle }/bare` ) .set( 'x-restbase-compat', 'true' ); assert.deepEqual( status, 404, text ); @@ -216,7 +238,7 @@ describe( 'Page Source', () => { describe( 'GET /page/{title}/html', () => { it( 'Title normalization should return permanent redirect (301)', async () => { - const { status, text, headers } = await client.get( `/page/${ redirectPage }/html`, { flavor: 'edit' } ); + const { status, text, headers } = await client.get( `${ pathPrefix }/page/${ redirectPage }/html`, { flavor: 'edit' } ); const { search } = parseURL( headers.location ); assert.include( search, 'flavor=edit' ); assert.deepEqual( status, 301, text ); @@ -225,7 +247,7 @@ describe( 'Page Source', () => { it( 'Wiki redirects should return temporary redirect (307)', async () => { const redirectPageDbkey = utils.dbkey( redirectPage ); const redirectedPageDbkey = utils.dbkey( redirectedPage ); - const { status, text, headers } = await client.get( `/page/${ redirectPageDbkey }/html`, { flavor: 'edit' } ); + const { status, text, headers } = await client.get( `${ pathPrefix }/page/${ redirectPageDbkey }/html`, { flavor: 'edit' } ); const { host, pathname, search } = parseURL( headers.location ); assert.include( search, 'flavor=edit' ); assert.include( pathname, `/page/${ redirectedPageDbkey }` ); @@ -236,35 +258,41 @@ describe( 'Page Source', () => { it( 'Variant redirects should return temporary redirect (307)', async () => { const agepayDbkey = utils.dbkey( agepay ); const atinlayAgepayDbkey = utils.dbkey( atinlayAgepay ); - const { status, text, headers } = await client.get( `/page/${ agepayDbkey }/html` ); + const { status, text, headers } = await client.get( `${ pathPrefix }/page/${ agepayDbkey }/html` ); assert.deepEqual( status, 307, text ); assert.include( headers.location, atinlayAgepayDbkey ); } ); it( 'Bypass wiki redirects with query param redirect=no', async () => { const redirectPageDbkey = utils.dbkey( redirectPage ); - const { status, text, headers } = await client.get( - `/page/${ redirectPageDbkey }/html`, + const res = await client.get( + `${ pathPrefix }/page/${ redirectPageDbkey }/html`, { redirect: 'no' } ); + const { status, text, headers } = res; assert.deepEqual( status, 200, text ); assert.match( headers[ 'content-type' ], /^text\/html/ ); + // eslint-disable-next-line no-unused-expressions + expect( res ).to.satisfyApiSpec; } ); it( 'Bypass wiki redirects with query param redirect=false', async () => { const redirectPageDbkey = utils.dbkey( redirectPage ); - const { status, text, headers } = await client.get( - `/page/${ redirectPageDbkey }/html`, + const res = await client.get( + `${ pathPrefix }/page/${ redirectPageDbkey }/html`, { redirect: 'false' } ); + const { status, text, headers } = res; assert.deepEqual( status, 200, text ); assert.match( headers[ 'content-type' ], /^text\/html/ ); + // eslint-disable-next-line no-unused-expressions + expect( res ).to.satisfyApiSpec; } ); it( 'Bypass variant redirects with query param redirect=no', async () => { const agepayDbkey = utils.dbkey( agepay ); const { status, headers } = await client.get( - `/page/${ agepayDbkey }/html`, + `${ pathPrefix }/page/${ agepayDbkey }/html`, { redirect: 'no' } ); assert.deepEqual( status, 404 ); @@ -273,32 +301,38 @@ describe( 'Page Source', () => { } ); it( 'Should successfully return page HTML', async () => { - const { status, headers, text } = await client.get( `/page/${ page }/html` ); + const res = await client.get( `${ pathPrefix }/page/${ page }/html` ); + const { status, headers, text } = res; assert.deepEqual( status, 200, text ); assert.match( headers[ 'content-type' ], /^text\/html/ ); assert.match( text, /<html\b/ ); assert.match( text, /Edit \w+<\/b>/ ); + // eslint-disable-next-line no-unused-expressions + expect( res ).to.satisfyApiSpec; } ); it( 'Should successfully return page HTML for a system message', async () => { const msg = 'MediaWiki:Newpage-desc'; - const { status, headers, text } = await client.get( `/page/${ msg }/html` ); + const res = await client.get( `${ pathPrefix }/page/${ msg }/html` ); + const { status, headers, text } = res; assert.deepEqual( status, 200, text ); assert.match( headers[ 'content-type' ], /^text\/html/ ); assert.match( text, /<html\b/ ); assert.match( text, /Start a new page/ ); + // eslint-disable-next-line no-unused-expressions + expect( res ).to.satisfyApiSpec; } ); it( 'Should return 404 error for non-existent page', async () => { const dummyPageTitle = utils.title( 'DummyPage_' ); - const { status } = await client.get( `/page/${ dummyPageTitle }/html` ); + const { status } = await client.get( `${ pathPrefix }/page/${ dummyPageTitle }/html` ); assert.deepEqual( status, 404 ); } ); it( 'Should have appropriate response headers', async () => { - const preEditResponse = await client.get( `/page/${ page }/html` ); + const preEditResponse = await client.get( `${ pathPrefix }/page/${ page }/html` ); const preEditDate = new Date( preEditResponse.headers[ 'last-modified' ] ); const preEditEtag = preEditResponse.headers.etag; await mindy.edit( page, { text: "'''Edit XYZ'''" } ); - const postEditResponse = await client.get( `/page/${ page }/html` ); + const postEditResponse = await client.get( `${ pathPrefix }/page/${ page }/html` ); const postEditDate = new Date( postEditResponse.headers[ 'last-modified' ] ); const postEditHeaders = postEditResponse.headers; const postEditEtag = postEditResponse.headers.etag; @@ -313,7 +347,7 @@ describe( 'Page Source', () => { } ); it( 'Should perform variant conversion', async () => { await mindy.edit( variantPage, { text: '<p>test language conversion</p>' } ); - const { headers, text } = await client.get( `/page/${ variantPage }/html`, null, { + const { headers, text } = await client.get( `${ pathPrefix }/page/${ variantPage }/html`, null, { 'accept-language': 'en-x-piglatin' } ); @@ -325,7 +359,7 @@ describe( 'Page Source', () => { } ); it( 'Should perform fallback variant conversion', async () => { await mindy.edit( fallbackVariantPage, { text: 'Podvlačenje linkova:' } ); - const { headers, text } = await client.get( `/page/${ encodeURIComponent( fallbackVariantPage ) }/html`, null, { + const { headers, text } = await client.get( `${ pathPrefix }/page/${ encodeURIComponent( fallbackVariantPage ) }/html`, null, { 'accept-language': 'sh-cyrl' } ); @@ -341,7 +375,7 @@ describe( 'Page Source', () => { it( 'Should successfully return restbase-compatible errors', async () => { const dummyPageTitle = utils.title( 'DummyPage_' ); const { status, body, text, headers } = await client - .get( `/page/${ dummyPageTitle }/html` ) + .get( `${ pathPrefix }/page/${ dummyPageTitle }/html` ) .set( 'x-restbase-compat', 'true' ); assert.deepEqual( status, 404, text ); @@ -352,7 +386,7 @@ describe( 'Page Source', () => { describe( 'GET /page/{title}/with_html', () => { it( 'Title normalization should return permanent redirect (301)', async () => { - const { status, text, headers } = await client.get( `/page/${ redirectPage }/with_html`, { flavor: 'edit' } ); + const { status, text, headers } = await client.get( `${ pathPrefix }/page/${ redirectPage }/with_html`, { flavor: 'edit' } ); const { search } = parseURL( headers.location ); assert.include( search, 'flavor=edit' ); assert.deepEqual( status, 301, text ); @@ -360,7 +394,7 @@ describe( 'Page Source', () => { it( 'Wiki redirects should return temporary redirect (307)', async () => { const redirectPageDbkey = utils.dbkey( redirectPage ); - const { status, text, headers } = await client.get( `/page/${ redirectPageDbkey }/with_html`, { flavor: 'edit' } ); + const { status, text, headers } = await client.get( `${ pathPrefix }/page/${ redirectPageDbkey }/with_html`, { flavor: 'edit' } ); const { search } = parseURL( headers.location ); assert.include( search, 'flavor=edit' ); assert.deepEqual( status, 307, text ); @@ -369,17 +403,21 @@ describe( 'Page Source', () => { it( 'Bypass redirects with query param redirect=no', async () => { const redirectPageDbkey = utils.dbkey( redirectPage ); const redirectedPageDbKey = utils.dbkey( redirectedPage ); - const { status, body: { redirect_target }, text, headers } = await client.get( - `/page/${ redirectPageDbkey }/with_html`, + const res = await client.get( + `${ pathPrefix }/page/${ redirectPageDbkey }/with_html`, { redirect: 'no' } ); + const { status, body: { redirect_target }, text, headers } = res; assert.match( redirect_target, new RegExp( `/page/${ encodeURIComponent( redirectedPageDbKey ) }/with_html` ) ); assert.deepEqual( status, 200, text ); assert.match( headers[ 'content-type' ], /^application\/json/ ); + // eslint-disable-next-line no-unused-expressions + expect( res ).to.satisfyApiSpec; } ); it( 'Should successfully return page HTML and metadata for Wikitext page', async () => { - const { status, body, text, headers } = await client.get( `/page/${ page }/with_html` ); + const res = await client.get( `${ pathPrefix }/page/${ page }/with_html` ); + const { status, body, text, headers } = res; assert.deepEqual( status, 200, text ); assert.match( headers[ 'content-type' ], /^application\/json/ ); assert.containsAllKeys( body, [ 'latest', 'id', 'key', 'license', 'title', 'content_model', 'html' ] ); @@ -388,10 +426,13 @@ describe( 'Page Source', () => { assert.nestedPropertyVal( body, 'key', utils.dbkey( page ) ); assert.match( body.html, /<html\b/ ); assert.match( body.html, /Edit \w+<\/b>/ ); + // eslint-disable-next-line no-unused-expressions + expect( res ).to.satisfyApiSpec; } ); it( 'Should successfully return page HTML and metadata for a system message', async () => { const msg = 'MediaWiki:Newpage-desc'; - const { status, body, text, headers } = await client.get( `/page/${ msg }/with_html` ); + const res = await client.get( `${ pathPrefix }/page/${ msg }/with_html` ); + const { status, body, text, headers } = res; assert.deepEqual( status, 200, text ); assert.match( headers[ 'content-type' ], /^application\/json/ ); assert.containsAllKeys( body, [ 'latest', 'id', 'key', 'license', 'title', 'content_model', 'html' ] ); @@ -402,19 +443,21 @@ describe( 'Page Source', () => { assert.nestedPropertyVal( body.latest, 'id', 0 ); assert.match( body.html, /<html\b/ ); assert.match( body.html, /Start a new page/ ); + // eslint-disable-next-line no-unused-expressions + expect( res ).to.satisfyApiSpec; } ); it( 'Should return 404 error for non-existent page', async () => { const dummyPageTitle = utils.title( 'DummyPage_' ); - const { status } = await client.get( `/page/${ dummyPageTitle }/with_html` ); + const { status } = await client.get( `${ pathPrefix }/page/${ dummyPageTitle }/with_html` ); assert.deepEqual( status, 404 ); } ); it( 'Should have appropriate response headers', async () => { - const preEditResponse = await client.get( `/page/${ page }/with_html` ); + const preEditResponse = await client.get( `${ pathPrefix }/page/${ page }/with_html` ); const preEditRevDate = new Date( preEditResponse.body.latest.timestamp ); const preEditEtag = preEditResponse.headers.etag; await mindy.edit( page, { text: "'''Edit ABCD'''" } ); - const postEditResponse = await client.get( `/page/${ page }/with_html` ); + const postEditResponse = await client.get( `${ pathPrefix }/page/${ page }/with_html` ); const postEditRevDate = new Date( postEditResponse.body.latest.timestamp ); const postEditHeaders = postEditResponse.headers; const postEditEtag = postEditResponse.headers.etag; @@ -432,7 +475,7 @@ describe( 'Page Source', () => { } ); it( 'Should perform variant conversion', async () => { await mindy.edit( variantPage, { text: '<p>test language conversion</p>' } ); - const { headers, text } = await client.get( `/page/${ variantPage }/html`, null, { + const { headers, text } = await client.get( `${ pathPrefix }/page/${ variantPage }/html`, null, { 'accept-language': 'en-x-piglatin' } ); @@ -450,7 +493,7 @@ describe( 'Page Source', () => { } ); it( 'Should perform fallback variant conversion', async () => { await mindy.edit( fallbackVariantPage, { text: 'Podvlačenje linkova:' } ); - const { headers, text } = await client.get( `/page/${ encodeURIComponent( fallbackVariantPage ) }/html`, null, { + const { headers, text } = await client.get( `${ pathPrefix }/page/${ encodeURIComponent( fallbackVariantPage ) }/html`, null, { 'accept-language': 'sh-cyrl' } ); @@ -470,7 +513,8 @@ describe( 'Page Source', () => { } ); // eslint-disable-next-line mocha/no-exports -exports.init = function ( pp ) { +exports.init = function ( pp, sm ) { // Allow testing both legacy and module paths using the same tests pathPrefix = pp; + specModule = sm; }; diff --git a/tests/api-testing/REST/content.v1/Revision.js b/tests/api-testing/REST/content.v1/Revision.js index 7cdd4eebc0c3..b63cab834667 100644 --- a/tests/api-testing/REST/content.v1/Revision.js +++ b/tests/api-testing/REST/content.v1/Revision.js @@ -64,7 +64,8 @@ describe( 'Revision', () => { describe( 'GET /revision/{id}/bare', () => { it( 'should successfully get information about revision', async () => { - const { status, body, text, headers } = await client.get( `${ pathPrefix }/revision/${ newrevid }/bare` ); + const res = await client.get( `${ pathPrefix }/revision/${ newrevid }/bare` ); + const { status, body, text, headers } = res; assert.strictEqual( status, 200, text ); assert.match( headers[ 'content-type' ], /^application\/json/ ); @@ -77,6 +78,9 @@ describe( 'Revision', () => { assert.isOk( headers.etag, 'etag' ); assert.equal( Date.parse( body.timestamp ), Date.parse( headers[ 'last-modified' ] ) ); assert.nestedProperty( body, 'html_url' ); + + // eslint-disable-next-line no-unused-expressions + expect( res ).to.satisfyApiSpec; } ); it( 'should return 404 for revision that does not exist', async () => { @@ -91,7 +95,6 @@ describe( 'Revision', () => { const { status, body, text, headers } = await client .get( `${ pathPrefix }/revision/${ newrevid }/bare` ) .set( 'x-restbase-compat', 'true' ); - assert.deepEqual( status, 200, text ); assert.match( headers[ 'content-type' ], /^application\/json/ ); assert.match( headers.vary, /\bx-restbase-compat\b/ ); @@ -116,9 +119,10 @@ describe( 'Revision', () => { describe( 'GET /revision/{id}/with_html', () => { it( 'should successfully get metadata and HTML of revision', async () => { - const { status, body, text, headers } = await client.get( + const res = await client.get( `${ pathPrefix }/revision/${ newrevid }/with_html` ); + const { status, body, text, headers } = res; assert.strictEqual( status, 200, text ); assert.match( headers[ 'content-type' ], /^application\/json/ ); @@ -138,6 +142,10 @@ describe( 'Revision', () => { const headerDate = Date.parse( headers[ 'last-modified' ] ); const revDate = Date.parse( body.timestamp ); assert.strictEqual( revDate.valueOf() <= headerDate.valueOf(), true ); + + // eslint-disable-next-line no-unused-expressions + expect( res ).to.satisfyApiSpec; + } ); it( 'should return 404 for revision that does not exist', async () => { @@ -167,9 +175,10 @@ describe( 'Revision', () => { describe( 'GET /revision/{id}/html', () => { it( 'should successfully get HTML of revision', async () => { - const { status, text, headers } = await client.get( + const res = await client.get( `${ pathPrefix }/revision/${ newrevid }/html` ); + const { status, text, headers } = res; assert.strictEqual( status, 200, text ); assert.containsAllKeys( headers, [ 'etag', 'cache-control', 'last-modified', 'content-type' ] ); @@ -208,7 +217,6 @@ describe( 'Revision', () => { const { body, headers } = await client .get( `${ pathPrefix }/revision/99999999/html` ) .set( 'x-restbase-compat', 'true' ); - assert.match( headers[ 'content-type' ], /^application\/json/ ); assert.containsAllKeys( body, [ 'type', 'title', 'method', 'detail', 'uri' ] ); } ); diff --git a/tests/api-testing/REST/content.v1/Update.js b/tests/api-testing/REST/content.v1/Update.js index c7e24880746b..31c0c3ece132 100644 --- a/tests/api-testing/REST/content.v1/Update.js +++ b/tests/api-testing/REST/content.v1/Update.js @@ -2,15 +2,28 @@ const { action, assert, REST, utils } = require( 'api-testing' ); -let pathPrefix = 'rest.php/content/v1'; +const chai = require( 'chai' ); +const expect = chai.expect; + +const chaiResponseValidator = require( 'chai-openapi-response-validator' ).default; + +let pathPrefix = '/content/v1'; +let specModule = '/content/v1'; describe( 'PUT /page/{title}', () => { - let client, mindy, mindyToken; + let client, mindy, mindyToken, openApiSpec; before( async () => { mindy = await action.mindy(); - client = new REST( pathPrefix, mindy ); + client = new REST( 'rest.php', mindy ); mindyToken = await mindy.token(); + + const specPath = '/specs/v0/module' + specModule; + const { status, text } = await client.get( specPath ); + assert.deepEqual( status, 200 ); + + openApiSpec = JSON.parse( text ); + chai.use( chaiResponseValidator( openApiSpec ) ); } ); const checkEditResponse = function ( title, reqBody, body ) { @@ -47,17 +60,22 @@ describe( 'PUT /page/{title}', () => { comment: 'tästing' }; - const { status: editStatus, body: editBody, header: editHeader } = - await client.put( `/page/${ title }`, reqBody ); + const res = + await client.put( `${ pathPrefix }/page/${ title }`, reqBody ); + const { status: editStatus, body: editBody, header: editHeader } = res; assert.equal( editStatus, 201 ); assert.match( editHeader[ 'content-type' ], /^application\/json/ ); checkEditResponse( title, reqBody, editBody ); const { status: sourceStatus, body: sourceBody, header: sourceHeader } = - await client.get( `/page/${ normalizedTitle }` ); + await client.get( `${ pathPrefix }/page/${ normalizedTitle }` ); assert.equal( sourceStatus, 200 ); assert.match( sourceHeader[ 'content-type' ], /^application\/json/ ); checkSourceResponse( title, reqBody, sourceBody ); + + // eslint-disable-next-line no-unused-expressions + expect( res ).to.satisfyApiSpec; + } ); it( 'should create a page with specific content model', async () => { @@ -72,17 +90,22 @@ describe( 'PUT /page/{title}', () => { content_model: 'wikitext' }; - const { status: editStatus, body: editBody, header: editHeader } = - await client.put( `/page/${ title }`, reqBody ); + const resEdit = await client.put( `${ pathPrefix }/page/${ title }`, reqBody ); + const { status: editStatus, body: editBody, header: editHeader } = resEdit; assert.equal( editStatus, 201 ); assert.match( editHeader[ 'content-type' ], /^application\/json/ ); + // eslint-disable-next-line no-unused-expressions + expect( resEdit ).to.satisfyApiSpec; + checkEditResponse( title, reqBody, editBody ); - const { status: sourceStatus, body: sourceBody, header: sourceHeader } = - await client.get( `/page/${ normalizedTitle }` ); + const resGet = await client.get( `${ pathPrefix }/page/${ normalizedTitle }` ); + const { status: sourceStatus, body: sourceBody, header: sourceHeader } = resGet; assert.equal( sourceStatus, 200 ); assert.match( sourceHeader[ 'content-type' ], /^application\/json/ ); checkSourceResponse( title, reqBody, sourceBody ); + // eslint-disable-next-line no-unused-expressions + expect( resGet ).to.satisfyApiSpec; } ); it( 'should update an existing page', async () => { @@ -91,7 +114,7 @@ describe( 'PUT /page/{title}', () => { // create await mindy.edit( normalizedTitle, {} ); - const { body: newPage } = await client.get( `/page/${ normalizedTitle }/bare` ); + const { body: newPage } = await client.get( `${ pathPrefix }/page/${ normalizedTitle }/bare` ); const firstRev = newPage.latest; @@ -101,19 +124,23 @@ describe( 'PUT /page/{title}', () => { comment: 'tästing', latest: firstRev // provide the base revision }; - const { status: editStatus, body: editBody, header: editHeader } = - await client.put( `/page/${ title }`, reqBody ); + const resEdit = await client.put( `${ pathPrefix }/page/${ title }`, reqBody ); + const { status: editStatus, body: editBody, header: editHeader } = resEdit; assert.equal( editStatus, 200 ); assert.match( editHeader[ 'content-type' ], /^application\/json/ ); checkEditResponse( title, reqBody, editBody ); assert.isAbove( editBody.latest.id, firstRev.id ); + // eslint-disable-next-line no-unused-expressions + expect( resEdit ).to.satisfyApiSpec; - const { status: sourceStatus, body: sourceBody, header: sourceHeader } = - await client.get( `/page/${ normalizedTitle }` ); + const resGet = await client.get( `${ pathPrefix }/page/${ normalizedTitle }` ); + const { status: sourceStatus, body: sourceBody, header: sourceHeader } = resGet; assert.equal( sourceStatus, 200 ); assert.match( sourceHeader[ 'content-type' ], /^application\/json/ ); checkSourceResponse( title, reqBody, sourceBody ); + // eslint-disable-next-line no-unused-expressions + expect( resGet ).to.satisfyApiSpec; } ); it( 'should handle null-edits (unchanged content) gracefully', async () => { @@ -128,7 +155,7 @@ describe( 'PUT /page/{title}', () => { comment: 'nothing at all changed', latest: { id: firstRev.newrevid } }; - const resp = await client.put( `/page/${ title }`, reqBody ); + const resp = await client.put( `${ pathPrefix }/page/${ title }`, reqBody ); const { status: editStatus, body: editBody, header: editHeader } = resp; assert.equal( editStatus, 200 ); @@ -138,6 +165,8 @@ describe( 'PUT /page/{title}', () => { // No revision was created, new ID is the same as the old ID assert.equal( editBody.latest.id, firstRev.newrevid ); + // eslint-disable-next-line no-unused-expressions + expect( resp ).to.satisfyApiSpec; } ); it( 'should automatically solve merge conflicts', async () => { @@ -156,14 +185,17 @@ describe( 'PUT /page/{title}', () => { content_model: 'wikitext', latest: { id: firstRev.newrevid } }; - const { status: editStatus, body: editBody, header: editHeader } = - await client.put( `/page/${ title }`, reqBody ); + const res = + await client.put( `${ pathPrefix }/page/${ title }`, reqBody ); + const { status: editStatus, body: editBody, header: editHeader } = res; assert.equal( editStatus, 200 ); assert.match( editHeader[ 'content-type' ], /^application\/json/ ); const expectedText = 'FIRST LINE\nlorem ipsum\nSECOND LINE'; assert.nestedPropertyVal( editBody, 'source', expectedText ); + // eslint-disable-next-line no-unused-expressions + expect( res ).to.satisfyApiSpec; } ); } ); @@ -183,12 +215,16 @@ describe( 'PUT /page/{title}', () => { const incompleteBody = { ...reqBody }; delete incompleteBody[ missingPropName ]; - const { status: editStatus, body: editBody, header: editHeader } = - await client.put( `/page/${ title }`, incompleteBody ); + const res = + await client.put( `${ pathPrefix }/page/${ title }`, incompleteBody ); + const { status: editStatus, body: editBody, header: editHeader } = res; assert.equal( editStatus, 400 ); assert.match( editHeader[ 'content-type' ], /^application\/json/ ); assert.nestedProperty( editBody, 'messageTranslations' ); + // eslint-disable-next-line no-unused-expressions + expect( res ).to.satisfyApiSpec; + } ); } ); @@ -200,12 +236,14 @@ describe( 'PUT /page/{title}', () => { comment: 'tästing' }; - const { status: editStatus, body: editBody, header: editHeader } = - await client.put( `/page/${ title }`, reqBody ); + const res = await client.put( `${ pathPrefix }/page/${ title }`, reqBody ); + const { status: editStatus, body: editBody, header: editHeader } = res; assert.equal( editStatus, 403 ); assert.match( editHeader[ 'content-type' ], /^application\/json/ ); assert.nestedProperty( editBody, 'messageTranslations' ); + // eslint-disable-next-line no-unused-expressions + expect( res ).to.satisfyApiSpec; } ); it( 'should fail if a bad token is given', async () => { @@ -216,12 +254,15 @@ describe( 'PUT /page/{title}', () => { comment: 'tästing' }; - const { status: editStatus, body: editBody, header: editHeader } = - await client.put( `/page/${ title }`, reqBody ); + const res = await client.put( `${ pathPrefix }/page/${ title }`, reqBody ); + const { status: editStatus, body: editBody, header: editHeader } = res; assert.equal( editStatus, 403 ); assert.match( editHeader[ 'content-type' ], /^application\/json/ ); assert.nestedProperty( editBody, 'messageTranslations' ); + // eslint-disable-next-line no-unused-expressions + expect( res ).to.satisfyApiSpec; + } ); it( 'should fail if a bad content model is given', async () => { @@ -233,12 +274,15 @@ describe( 'PUT /page/{title}', () => { comment: 'tästing', content_model: 'THIS DOES NOT EXIST!' }; - const { status: editStatus, body: editBody, header: editHeader } = - await client.put( `/page/${ title }`, reqBody ); + const res = + await client.put( `${ pathPrefix }/page/${ title }`, reqBody ); + const { status: editStatus, body: editBody, header: editHeader } = res; assert.equal( editStatus, 400 ); assert.match( editHeader[ 'content-type' ], /^application\/json/ ); assert.nestedProperty( editBody, 'messageTranslations' ); + // eslint-disable-next-line no-unused-expressions + expect( res ).to.satisfyApiSpec; } ); it( 'should fail if a bad title is given', async () => { @@ -250,12 +294,15 @@ describe( 'PUT /page/{title}', () => { content_model: 'wikitext', token: mindyToken }; - const { status: editStatus, body: editBody, header: editHeader } = - await client.put( `/page/${ title }`, reqBody ); + const res = + await client.put( `${ pathPrefix }/page/${ title }`, reqBody ); + const { status: editStatus, body: editBody, header: editHeader } = res; assert.equal( editStatus, 400 ); assert.match( editHeader[ 'content-type' ], /^application\/json/ ); assert.nestedProperty( editBody, 'messageTranslations' ); + // eslint-disable-next-line no-unused-expressions + expect( res ).to.satisfyApiSpec; } ); it( 'should fail if no title is given', async () => { @@ -265,12 +312,15 @@ describe( 'PUT /page/{title}', () => { content_model: 'wikitext', token: mindyToken }; - const { status: editStatus, body: editBody, header: editHeader } = - await client.put( '/page/', reqBody ); + const res = + await client.post( `${ pathPrefix }/page`, reqBody ); + const { status: editStatus, body: editBody, header: editHeader } = res; assert.equal( editStatus, 400 ); assert.match( editHeader[ 'content-type' ], /^application\/json/ ); assert.nestedProperty( editBody, 'messageTranslations' ); + // eslint-disable-next-line no-unused-expressions + expect( res ).to.satisfyApiSpec; } ); } ); @@ -284,12 +334,15 @@ describe( 'PUT /page/{title}', () => { comment: 'tästing', latest: { id: 1234 } }; - const { status: editStatus, body: editBody, header: editHeader } = - await client.put( `/page/${ title }`, reqBody ); + const res = await client.put( `${ pathPrefix }/page/${ title }`, reqBody ); + const { status: editStatus, body: editBody, header: editHeader } = res; assert.equal( editStatus, 404 ); assert.match( editHeader[ 'content-type' ], /^application\/json/ ); assert.nestedProperty( editBody, 'messageTranslations' ); + // eslint-disable-next-line no-unused-expressions + expect( res ).to.satisfyApiSpec; + } ); it( 'should detect a conflict if page exist but no revision ID was given', async () => { @@ -304,12 +357,15 @@ describe( 'PUT /page/{title}', () => { comment: 'tästing' // not 'latest' key, so page should be created }; - const { status: editStatus, body: editBody, header: editHeader } = - await client.put( `/page/${ title }`, reqBody ); + const res = await client.put( `${ pathPrefix }/page/${ title }`, reqBody ); + const { status: editStatus, body: editBody, header: editHeader } = res; assert.equal( editStatus, 409 ); assert.match( editHeader[ 'content-type' ], /^application\/json/ ); assert.nestedProperty( editBody, 'messageTranslations' ); + // eslint-disable-next-line no-unused-expressions + expect( res ).to.satisfyApiSpec; + } ); it( 'should detect a conflict when an old base revision ID is given and conflict resolution fails', async () => { @@ -325,12 +381,15 @@ describe( 'PUT /page/{title}', () => { comment: 'tästing', latest: { id: firstRev.newrevid } }; - const { status: editStatus, body: editBody, header: editHeader } = - await client.put( `/page/${ title }`, reqBody ); + const res = await client.put( `${ pathPrefix }/page/${ title }`, reqBody ); + const { status: editStatus, body: editBody, header: editHeader } = res; assert.equal( editStatus, 409 ); assert.match( editHeader[ 'content-type' ], /^application\/json/ ); assert.nestedProperty( editBody, 'messageTranslations' ); + // eslint-disable-next-line no-unused-expressions + expect( res ).to.satisfyApiSpec; + } ); } ); @@ -353,18 +412,22 @@ describe( 'PUT /page/{title}', () => { comment: 'tästing', latest: { id: firstRev.newrevid } }; - const { status: editStatus, body: editBody, header: editHeader } = - await client.put( `/page/${ title }`, reqBody ); + const res = await client.put( `${ pathPrefix }/page/${ title }`, reqBody ); + const { status: editStatus, body: editBody, header: editHeader } = res; assert.equal( editStatus, 403 ); assert.match( editHeader[ 'content-type' ], /^application\/json/ ); assert.nestedProperty( editBody, 'messageTranslations' ); + // eslint-disable-next-line no-unused-expressions + expect( res ).to.satisfyApiSpec; + } ); } ); } ); // eslint-disable-next-line mocha/no-exports -exports.init = function ( pp ) { +exports.init = function ( pp, sm ) { // Allow testing both legacy and module paths using the same tests pathPrefix = pp; + specModule = sm; }; |