From b93ea4843e9e18e52e00317f8379f4ca9df91395 Mon Sep 17 00:00:00 2001 From: Fredrick Whorton Date: Fri, 20 Mar 2015 13:43:39 -0400 Subject: [PATCH 1/6] Update tests per responses in email. --- test/v1_0_2/document.js | 2 +- test/v1_0_2/non_templating.js | 49 +++++++++++++++++++---------------- 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/test/v1_0_2/document.js b/test/v1_0_2/document.js index 1e9a9d1..20adec6 100644 --- a/test/v1_0_2/document.js +++ b/test/v1_0_2/document.js @@ -1482,7 +1482,7 @@ }); }); - it('A Person Object uses a "mbox_sha1sum" property at most one time (Multiplicity, 7.6.table1.row4.c)', function () { + it('A Person Object uses a "mbox_sha1sum" property at most one time (Multiplicity, 7.6.table1.row4.c)', function (done) { // JSON Parser validation done(); }); diff --git a/test/v1_0_2/non_templating.js b/test/v1_0_2/non_templating.js index 7606889..31c28ac 100644 --- a/test/v1_0_2/non_templating.js +++ b/test/v1_0_2/non_templating.js @@ -263,7 +263,7 @@ }); }); - describe.skip('An LRS rejects with error code 400 Bad Request, a PUT or POST Request which uses Attachments, has a "Content Type" header with value "multipart/mixed", and does not have a body header named "Content-Type" with value "multipart/mixed" (RFC 1341)', function () { + describe('An LRS rejects with error code 400 Bad Request, a PUT or POST Request which uses Attachments, has a "Content Type" header with value "multipart/mixed", and does not have a body header named "Content-Type" with value "multipart/mixed" (RFC 1341)', function () { it('should fail when attachment is raw data and first part content type is not "application/json"', function (done) { var header = {'Content-Type': 'multipart/mixed; boundary=-------314159265358979323846'}; var attachment = fs.readFileSync('test/v1_0_2/templates/attachments/basic_text_multipart_attachment_invalid_first_part_content_type.part', {encoding: 'binary'}); @@ -275,7 +275,7 @@ }); }); - describe.skip('An LRS rejects with error code 400 Bad Request, a PUT or POST Request which uses Attachments, has a "Content Type" header with value "multipart/mixed", and does not have a body header named "boundary" (4.1.11.b, RFC 1341)', function () { + describe('An LRS rejects with error code 400 Bad Request, a PUT or POST Request which uses Attachments, has a "Content Type" header with value "multipart/mixed", and does not have a body header named "boundary" (4.1.11.b, RFC 1341)', function () { it('should fail if boundary not provided in body', function (done) { var header = {'Content-Type': 'multipart/mixed; boundary=-------314159265358979323846'}; var attachment = fs.readFileSync('test/v1_0_2/templates/attachments/basic_text_multipart_attachment_invalid_first_part_no_boundary.part', {encoding: 'binary'}); @@ -4053,39 +4053,52 @@ done(); }); - it('An LRS doesn\'t make any adjustments to incoming Statements that are not specifically mentioned in this section (4.1.12.d, Varies)', function (done) { - done(new Error('Implement Test')); + it('An LRS\'s Statement API rejects with Error Code 400 Bad Request any DELETE request (7.2)', function (done) { + // Using requirement: An LRS rejects with error code 405 Method Not Allowed to any request to an API which uses a method not in this specification **Implicit ONLY in that HTML normally does this behavior** + done(); }); - it('An LRS stores 32-bit floating point numbers with at least the precision of IEEE 754 (4.1.12.d.a)', function (done) { - done(new Error('Implement Test')); + it('A POST request is defined as a "pure" POST, as opposed to a GET taking on the form of a POST (7.2.2.e)', function (done) { + // All of these "defined" aren't really tests, rather ways to disambiguate future tests. + done(); }); - it('An LRS rejects with error code 400 Bad Request, a Request whose "authority" is a Group and consists of non-O-Auth Agents (4.1.9.a)', function (done) { - done(new Error('Implement Test')); + it('An LRS rejects with error code 400 Bad Request, a GET Request which uses Attachments, has a "Content-Type" header with value "application/json", and has the "attachments" filter attribute set to "true" (4.1.11.a)', function (done) { + // Not concerned with "Content-Type" when use a GET request + done(); }); - it('An LRS rejects with error code 403 Forbidden a Request whose "authority" is a Agent or Group that is not authorized (4.1.9.b, 6.4.2)', function (done) { + it('An LRS\'s Statement API will reject a GET request having the "attachment" parameter set to "true" if it does not follow the rest of the attachment rules (7.2.3.d)', function (done) { done(new Error('Implement Test')); }); - it('An LRS rejects with error code 400 Bad Request, a GET Request which uses Attachments, has a "Content-Type" header with value "application/json", and has the "attachments" filter attribute set to "true" (4.1.11.a)', function (done) { + it('An LRS\'s Statement API will reject a GET request having the "attachment" parameter set to "false" if it includes attachment raw data (7.2.3.e)', function (done) { done(new Error('Implement Test')); }); + it('An LRS\'s Statement API will reject a GET request having the "attachment" parameter set to "false" and the Content-Type field in the header set to anything but "application/json" (7.2.3.d) (7.2.3.e)', function (done) { + // Not concerned with "Content-Type" when use a GET request + done(); + }); + it('An LRS rejects with error code 400 Bad Request, a PUT or POST Request which uses Attachments, has a "Content Type" header with value "multipart/mixed", and does not have a body header named "MIME-Version" with a value of "1.0" or greater (4.1.11.b, RFC 1341)', function (done) { + // RFC 1341: MIME-Version header field is required at the top level of a message. It is not required for each body part of a multipart entity + done(); + }); + + it('An LRS doesn\'t make any adjustments to incoming Statements that are not specifically mentioned in this section (4.1.12.d, Varies)', function (done) { done(new Error('Implement Test')); }); - it('An LRS\'s Statement API will reject a GET request having the "attachment" parameter set to "true" if it does not follow the rest of the attachment rules (7.2.3.d)', function (done) { + it('An LRS stores 32-bit floating point numbers with at least the precision of IEEE 754 (4.1.12.d.a)', function (done) { done(new Error('Implement Test')); }); - it('An LRS\'s Statement API will reject a GET request having the "attachment" parameter set to "false" if it includes attachment raw data (7.2.3.e)', function (done) { + it('An LRS rejects with error code 400 Bad Request, a Request whose "authority" is a Group and consists of non-O-Auth Agents (4.1.9.a)', function (done) { done(new Error('Implement Test')); }); - it('An LRS\'s Statement API will reject a GET request having the "attachment" parameter set to "false" and the Content-Type field in the header set to anything but "application/json" (7.2.3.d) (7.2.3.e)', function (done) { + it('An LRS rejects with error code 403 Forbidden a Request whose "authority" is a Agent or Group that is not authorized (4.1.9.b, 6.4.2)', function (done) { done(new Error('Implement Test')); }); @@ -4125,14 +4138,6 @@ done(new Error('Implement Test')); }); - it('An LRS\'s Statement API rejects with Error Code 400 Bad Request any DELETE request (7.2)', function (done) { - done(new Error('Implement Test')); - }); - - it('A POST request is defined as a "pure" POST, as opposed to a GET taking on the form of a POST (7.2.2.e)', function (done) { - done(new Error('Implement Test')); - }); - it('A GET request is defined as either a GET request or a POST request containing a GET request (7.2.3, 7.2.2.e)', function (done) { done(new Error('Implement Test')); }); @@ -4146,12 +4151,10 @@ }); it('A "statements" property which is too large for a single page will create a container for each additional page (4.2.table1.row1.b)', function (done) { - // TODO What determines to large? Property to set? done(new Error('Implement Test')); }); it('A "more" property IRL is accessible for at least 24 hours after being returned (4.2.a)', function (done) { - // TODO Skipping for now done(new Error('Implement Test')); }); }); From 4c1204c3c155fd41323a885d0e90fdeca760ea82 Mon Sep 17 00:00:00 2001 From: Fredrick Whorton Date: Fri, 17 Apr 2015 15:56:43 -0400 Subject: [PATCH 2/6] Update multipartParser, fix tests. --- package.json | 1 + test/multipartParser.js | 210 ++++++++++++++++++++++++++++++++++ test/v1_0_2/non_templating.js | 63 ++++++++-- 3 files changed, 262 insertions(+), 12 deletions(-) create mode 100644 test/multipartParser.js diff --git a/package.json b/package.json index 4b50dde..1432a35 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "querystring": "^0.2.0", "qs": "^2.3.3", "form-urlencoded": "0.0.7", + "string": "^3.1.0", "supertest-as-promised": "^1.0.0", "valid-url": "^1.0.9" } diff --git a/test/multipartParser.js b/test/multipartParser.js new file mode 100644 index 0000000..a06adda --- /dev/null +++ b/test/multipartParser.js @@ -0,0 +1,210 @@ +(function (module, S) { + 'use strict'; + + var headerParts = [ + { + field: 'contentType', + fn: function (part) { + var s = S(part.toLowerCase()); + + var match; + if (s.startsWith('content-type:')) { + var type = part.substring('content-type:'.length).trim(); + var regExp = new RegExp('\\w+\/[\\w.\\-\\+]+'); + var matches = regExp.exec(type); + if (Array.isArray(matches)) { + match = matches[0]; + } + } + return match; + } + }, + { + field: 'boundary', + fn: function (part) { + var s = S(part); + + var match; + if (s.contains('boundary=')) { + var index = s.toString().indexOf('boundary='); + var indexSemicolon = s.toString().indexOf(';', index); + var endIndex = (indexSemicolon < 0 ? s.toString().length : indexSemicolon); + match = part.substring(index + 'boundary='.length, endIndex); + } + return match; + } + }, + { + field: 'contentTransferEncoding', + fn: function (part) { + var s = S(part.toLowerCase()); + + var match; + if (s.startsWith('content-transfer-encoding:')) { + match = part.substring('content-transfer-encoding:'.length).trim(); + } + return match; + } + }, + { + field: 'contentDisposition', + fn: function (part) { + var s = S(part.toLowerCase()); + + var match; + if (s.startsWith('content-disposition:')) { + var type = part.substring('content-disposition:'.length).trim(); + var regExp = new RegExp('\\w+'); + var matches = regExp.exec(type); + if (Array.isArray(matches)) { + match = matches[0]; + } + } + return match; + } + }, + { + field: 'filename', + fn: function (part) { + var s = S(part.toLowerCase()); + + var match; + if (s.startsWith('content-disposition:') && s.contains('filename="')) { + var index = s.toString().indexOf('filename="'); + var filename = part.substring(index + 'filename="'.length); + + var regExp = new RegExp('[\\w\\W+\\w+][^"]+'); + var matches = regExp.exec(filename); + if (Array.isArray(matches)) { + match = matches[0]; + } + } + return match; + } + } + ]; + + function findDelimiter(content) { + var delimiter; // Delimiter for Windows, Linux, Mac + if (S(content).startsWith('\r\n')) { + delimiter = '\r\n'; + } else if (S(content).startsWith('\r')) { + delimiter = '\r'; + } else if (S(content).startsWith('\n')) { + delimiter = '\n'; + } else { + throw new Error('Multipart: unknown delimiter.'); + } + return delimiter; + } + + function parsePart(delimiter, content) { + var parsed = {}; + parsed.header = parseHeader(delimiter, content); + + var index = content.indexOf(delimiter + delimiter); + parsed.body = content.substring(index + (delimiter.length * 2), content.length - delimiter.length); + return parsed; + } + + function parseHeader(delimiter, content) { + var regExp = new RegExp(delimiter); + var parts = content.split(regExp); + + var header = {parts: []}; + if (parts.length < 2) { + throw new Error('Multipart: cannot parse header with invalid length.'); + } else if (!S(parts[0]).isEmpty()) { + throw new Error('Multipart: cannot parse header with invalid value.'); + } else if (S(parts[0]).isEmpty() && S(parts[1]).isEmpty()) { + header.parts.push('Content-Type: text/plain'); + header.contentType = 'text/plain'; + return header; + } + + for (var i = 1; i < parts.length; i++) { + var part = parts[i]; + if (S(part).isEmpty()) { + return header; + } else { + parseHeaderParts(header, part); + header.parts.push(part); + } + } + return header; + } + + function parseHeaderParts(header, part) { + headerParts.forEach(function (one) { + var value = one.fn(part); + if (value) { + header[one.field] = value; + } + }); + } + + + /** + * Searches in string to find boundary. + * @param {String} string - String to search + * @return {String} + */ + module.exports.getBoundary = function getBoundary(string) { + var boundary = ''; + + for (var i = 0; i < headerParts.length; i++) { + var header = headerParts[i]; + + if (header.field === 'boundary') { + boundary = header.fn(string); + break; + } + } + return boundary; + }; + + /** + * Parses multipart/mixed content (http://www.w3.org/Protocols/rfc1341/7_2_Multipart.html). This does not parse streams. + * + * { + * header; { + * parts: ['each header line since not all are mapped'], + * contentType: 'extracted Content-Type', + * contentTransferEncoding: 'extracted Content-Transfer-Encoding', + * contentDisposition: 'extracted Content-Disposition', + * filename: 'extracted filename' + * } + * body: 'extracted body from part' + * } + * + * @param {String} boundary - Boundary defined in header + * @param {String} body - Request body + */ + module.exports.parseMultipart = function parseMultipart(boundary, body) { + var dashedBoundary = '--' + boundary; + var index = body.indexOf(dashedBoundary); + if (index < 0) { + throw new Error('Multipart: boundary not found.'); + } + + var delimiter = findDelimiter(body.substring(index + dashedBoundary.length)); + + var regExp = new RegExp('--*' + boundary); + var contents = body.split(regExp); + + var parts = []; + var lastBoundary = false; + for (var i = 1; i < contents.length; i++) { + var content = contents[i]; + + if (S(content).startsWith('--' + delimiter) || S(content).startsWith('--')) { + lastBoundary = true; + } + + if (!lastBoundary) { + parts.push(parsePart(delimiter, content)); + } + } + return parts; + }; +}(module, require('string'))); \ No newline at end of file diff --git a/test/v1_0_2/non_templating.js b/test/v1_0_2/non_templating.js index 31c28ac..29016d9 100644 --- a/test/v1_0_2/non_templating.js +++ b/test/v1_0_2/non_templating.js @@ -5,7 +5,7 @@ * https://github.com/adlnet/xAPI_LRS_Test/blob/master/TestingRequirements.md * */ -(function (module, fs, extend, moment, request, requestPromise, qs, should, helper, validUrl) { +(function (module, fs, extend, moment, request, requestPromise, qs, should, validUrl, helper, multipartParser) { "use strict"; describe('An LRS populates the "authority" property if it is not provided in the Statement, based on header information with the Agent corresponding to the user (contained within the header) (Implicit, 4.1.9.b, 4.1.9.c)', function () { @@ -1083,7 +1083,18 @@ describe('A "more" property is an IRL (Format, 4.2.table1.row2.a)', function () { it('should return "more" property as an IRL', function (done) { + var templates = [ + {statement: '{{statements.default}}'} + ]; + var data = createFromTemplate(templates); + var statement = data.statement; + request(helper.getEndpoint()) + .post(helper.getEndpointStatements()) + .headers(helper.addHeaderXapiVersion({})) + .json([statement, statement]) + .expect(200) + .end() .get(helper.getEndpointStatements() + '?limit=1') .headers(helper.addHeaderXapiVersion({})) .expect(200) @@ -3143,7 +3154,7 @@ ]; var data = createFromTemplate(templates); - var query = qs.stringify(data); + var query = qs.stringify(data.agent); request(helper.getEndpoint()) .get(helper.getEndpointStatements() + '?' + query) .headers(helper.addHeaderXapiVersion({})) @@ -3413,27 +3424,55 @@ }); it('should return StatementResult with statements as array using GET with "attachments"', function (done) { - var query = qs.stringify({attachments: true}); + var header = {'Content-Type': 'multipart/mixed; boundary=-------314159265358979323846'}; + var attachment = fs.readFileSync('test/v1_0_2/templates/attachments/basic_text_multipart_attachment_valid.part', {encoding: 'binary'}); + request(helper.getEndpoint()) - .get(helper.getEndpointStatements() + '?' + query) - .headers(helper.addHeaderXapiVersion({})) - .expect(200) - .end(function (err, res) { + .post(helper.getEndpointStatements()) + .headers(helper.addHeaderXapiVersion(header)) + .body(attachment).expect(200).end(function (err, res) { if (err) { done(err); } else { try { var result = JSON.parse(res.body); - if (result.statements && Array.isArray(result.statements)) { - done(); + if (Array.isArray(result) && result.length > 0) { + var query = qs.stringify({ statementId: result[0], attachments: true}); + + request(helper.getEndpoint()) + .get(helper.getEndpointStatements() + '?' + query) + .headers(helper.addHeaderXapiVersion({})) + .expect(200) + .end(function (err, res) { + if (err) { + done(err); + } else { + try { + var boundary = multipartParser.getBoundary(res.headers['content-type']); + if (boundary) { + var parsed = multipartParser.parseMultipart(boundary, res.body); + var result = JSON.parse(parsed[0].body); + if (result.statements && Array.isArray(result.statements)) { + done(); + } else { + done(new Error('Statement "GET" does not return StatementResult.')); + } + } else { + done(new Error('Statement "GET" does not contain boundary in content-type.')); + } + } catch (error) { + done(error); + } + } + }); } else { - done(new Error('Statement "GET" does not return StatementResult.')); + done(new Error('Statement "POST" did not return array of IDs.')); } } catch (error) { done(error); } } - }); + }) }); }); @@ -4167,4 +4206,4 @@ return mockObject; } -}(module, require('fs'), require('extend'), require('moment'), require('super-request'), require('supertest-as-promised'), require('qs'), require('should'), require('./../helper'), require('valid-url'))); +}(module, require('fs'), require('extend'), require('moment'), require('super-request'), require('supertest-as-promised'), require('qs'), require('should'), require('valid-url'), require('./../helper'), require('./../multipartParser'))); From c1aa7dcad36bc5d0c3b74a30cae3b1903f21f7ef Mon Sep 17 00:00:00 2001 From: Fredrick Whorton Date: Thu, 23 Apr 2015 13:07:36 -0400 Subject: [PATCH 3/6] Update image to match SHA2. --- ...asic_image_multipart_attachment_valid.part | Bin 14856 -> 13400 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/test/v1_0_2/templates/attachments/basic_image_multipart_attachment_valid.part b/test/v1_0_2/templates/attachments/basic_image_multipart_attachment_valid.part index c83d8dfdffaed4f8cfae4ac27147539e743e7bcd..830d4e77072f32afed6484ce972b23ec6a7fe273 100644 GIT binary patch literal 13400 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 z z;z#3C^MnkZ{G^ikLq>_dG1(u7-`=iwuDe~LN5#b5D zDI44$kscVG6Ca=5;Iu8s!=sb>M>#o<)%PVqZ#O_Ry$MIx95z?QKlwN^`22|hyV}$a#Aw*QD_uG^FakRJaf}SIwyEOA>nL z!6hcffSUaRmV=iym1aUd3)3?-7Q{xYjZHHa<>Hwotf(Ij#W0aZRb4gKt*lIc}ne1}94wYT%P zc2_nLIae;VP1DIzYPs#=1<5AgRDX)lVH^w7T5C>m& zIz-?5@6yf2n(Ij%Itw-o-hM_@0mo|D+eR_#}bzOsjF}A4^ zqD=U3USqqQ3v!}l>pqNKfO*OS(buq6t6=@8^R;1xS9;`SisOE5;%t9E8tL2zyli06 zT#h1_nA?wn-8W1FwUa8z*vhsKLVYMD*%X>LSVBXjk^>NL;w_fH-cR{L@%-8nX*Nr# zR~ONm9FR(Lc-|{ot0PEDD?7?^(tP$OoMT}(vF6b1$L==As?p=nmc8oNeOoB0x^2t; z3-y`OW*-LETewy3%$te&wd~990xpc!$Bd7qjpD;weH*`(=!+!Fi2IJ$J2LRYir+FM2@;I6nhCMk%QXJ?==ci6Jn^nQm` zjDb3klv1L@ooE{S;d9sBV<$QKXk5?i&1{qWMv!l9Hy;#L$aGLX$(O&J{kf$0l$>(m|pg1>Ar=#znj98q_Q*aQesdC zBA&m4TXw-O3TGlRjW?Y>&IcAtalL!{Y_C>ImKO!O07Eh_y5iH_rYV`dq z>ABP&=EZ{=zz`u_lkC#kJvve*zFP*Yx91xgb@DRs>BE?$xZtpoQEL1xdlCnk3TjBZ zVc}8fL|cnGzD1@qhwH+PyzcKy`~`r|PZ|XlIWuEqrr)=cUrPiJFZZVh%5lj^$fC98QJ}~{wkHW_!^TO z$pt=RWj4_Or^?c>vZ+`JWXVUc?*pp)S9>vPigD~#BpZS{4_*-N7nUZGH8D=h6_PVi z=z**QMC`MyxhPLDm1md|WhE!y8lkHE*(+K!SM)j=RIhP>r&d%y5iLHnv_)y#XCZ6e zfS7gOjy0Ag#QXA%+fn-a`$@eQkvQ+YV@Kvy@3{%FEMpqh-ZL*Y%kCP<6Q}^P8&9vs zM{?~wn(Wz-W*brnXza|LE6&@FxXmvK+z}J*O1M6XMm9B#yGz>utek$5J(-D08Od}b z!1C#oJc7-~8EJG7eK9kyFpVqiVJ^EtS8rA#@ZuJ`^W({7qy0hJrjh^wOZa#vzrc9N zNUpxl!0)Hi|HWpW<8+Yo&-lZ@9|ry~@P~mv4E$l>4+DP~_`|>-2L3Schk-u~{9)h^ S1AiF!|H;7r^iK#N@P7dxDrY?a literal 14856 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

{ZdnY>zkO5j`Ar;-?#i|&%tk|=> zU+5mXz6=(90mx$jv$`BcetuGojH7M=3{XkM&rSNMo&u=U*_vAFSfy>eIUn_^az4dJ zD&GB06?3GduIr8vG687B00{7L%|yN}A8pM4@x;+N_J(>@uBojUM1T%-Y)?yt z6wt2@RYo#19IjGG4IxyNGQH3lI5!()YhPO?1aOw--Gx&h-@9Mce7`Im^p-(k8mw#2 zrtH1^2nG(BDA?Kpg?lp{K70y{)e#R`0s$+AlG+th8#ESld@b0mA3EOUQm8)I8~Gl< z?!C$@X3H4dqiIdC^sS&hPIYArr*bW;49~7g?ytcQt>qwC3W!&UFlO*JEKk(|lpk!E z;m2AR2L=S#H~<5vuEBg7LZw6pi^Tx~xEHWK7ORI~CTchqD{5`WuO84QMX^F8iW=Sy z`N`w{-dpM+wu1o>_d7BSHnRo>jMOzS_xc0V-e9-NnwI&61(7977UjV38U0^=^a^HG zuIgAEz>#-f<<002%B5khZ=JrTdYA?vY zHpC3naf#05DpO|2`QD=tXFxza(}Md6dPLM*q_MdywbhzDnFCh|tc~-bu?-}Y(IaCr z&kQnvCb`O*pPB>%)~*CUeZg-EhLgX)he=zy04g4dzBf zD7Wac9U!2PVNV&f2Y->!-lwv}fJwGK&n@l=)Gn)l9d>6GFv`R9k%s!QlUKiZTpL2! zh(k}fHk(idwIk19D}|(SJbM30uvHMtOSv?P#|z@D26_$15J4{i>U5?Ua99K{y9>my zs>h-69<}C}BM^VS=ZUTl9_Uj38^H`K1MtwC&2VS~uACUshN5_t8Qfyq+uy4SJ&uW- zmIL6y-#kZdX`{W#00Cf4wU{_wVv;EYS@cuC%AbVet$+8G#3FP3TK=XdE z{3UXr;txN1gi!=y`*=0pJ2j29QeHj-4&*UcZ!kLnS~^HS@rRsa?dHfh*4rwbqk082 z_L{}uf5YmB2J8b3h}x}o#VFqvG;XZFuzGhxXqq=*H1k1oAihWw1T%&NQQmq1QZ?%3 z8t5%-TCA;!nboq3FZV-h^;~&awX*j9rG-pPX{d$f0G<5kM_Dqd7ZD-C+V5np(h}RE z`OWi?gGp<$*661TfHY8xsLVp9eFZc10585A{Ea0PdRXOs04lH!Qidf44lgg4Uwj-h ztDqtlB604uSX-y)!I<&qc;I*c_@e3#+|d?eYpCA|rA=0CBHBX@WXF*(Hzp0BLJ*xQ zm<+@n{ouipmBAA-4hBtBmCc=I42$~ce+`L|X?q)wWvcYSH2WS95D|)^jKSjrjk6JR z)zq9|cT_jV?R=A~fN9$vQH0Z0w$W3d0#G&xP!C5lRc$i$(RU$hP1TjPiE0MpCRr2C zK&h_{w9KTzea3JmRhH^h%*2a7v`;au_w&Y!YC+u(GyLfe7;~&;D1$uf0rjA0s`Ea?)gKB2%M{l&e{}d$8vx4^aHyha0Xv}6<)JwcuN6cZy zd#0@cJZqH6TG-nG4(RM6)#KcT+xl)i4?ZDl!q>}~wTxDClRB+PTTkvvNT#(rT6YxF|^s6uZ%d0PoxQ9e9cl?(hTM!}2)M3C0ULXb3xyARS^4^Tq+9S&dmsLlA%}8~wYlacRUOv-y1J e_DgoUW#hkGyyO3xkl(GU@5KM`Kd+h1G5-l;6`3*s From ba13c6a3fe3be1226d470318cf262ab2e921edf0 Mon Sep 17 00:00:00 2001 From: Fredrick Whorton Date: Fri, 24 Apr 2015 14:28:44 -0400 Subject: [PATCH 4/6] Update clean up non-templating tests and use chai. --- package.json | 10 +- test/v1_0_2/non_templating.js | 1178 ++++++++++----------------------- 2 files changed, 356 insertions(+), 832 deletions(-) diff --git a/package.json b/package.json index 1432a35..b79bf43 100644 --- a/package.json +++ b/package.json @@ -20,13 +20,14 @@ }, "homepage": "https://github.com/TryxAPI/lrs-conformance-tests", "dependencies": { - "chai": "<1.10.0", + "chai": "^1.9.2", "chai-as-promised": "^4.1.1", "commander": "^2.6.0", "crypto": "0.0.3", "dirty-chai": "^1.0.0", "exit": "^0.1.2", "extend": "^2.0.0", + "form-urlencoded": "0.0.7", "joi": "^5.1.0", "lodash-node": "^3.2.0", "mocha": "^1.20.1", @@ -34,14 +35,13 @@ "node-env-file": "0.1.3", "node-uuid": "^1.4.1", "q": "^1.1.2", + "qs": "^2.3.3", + "querystring": "^0.2.0", "request": "^2.37.0", "should": "^4.0.4", + "string": "^3.1.0", "super-request": "0.0.8", "supertest": "^0.13.0", - "querystring": "^0.2.0", - "qs": "^2.3.3", - "form-urlencoded": "0.0.7", - "string": "^3.1.0", "supertest-as-promised": "^1.0.0", "valid-url": "^1.0.9" } diff --git a/test/v1_0_2/non_templating.js b/test/v1_0_2/non_templating.js index 29016d9..fb16dbf 100644 --- a/test/v1_0_2/non_templating.js +++ b/test/v1_0_2/non_templating.js @@ -5,9 +5,11 @@ * https://github.com/adlnet/xAPI_LRS_Test/blob/master/TestingRequirements.md * */ -(function (module, fs, extend, moment, request, requestPromise, qs, should, validUrl, helper, multipartParser) { +(function (module, fs, extend, moment, request, requestPromise, qs, chai, validUrl, helper, multipartParser) { "use strict"; + var expect = chai.expect; + describe('An LRS populates the "authority" property if it is not provided in the Statement, based on header information with the Agent corresponding to the user (contained within the header) (Implicit, 4.1.9.b, 4.1.9.c)', function () { it('should populate authority', function (done) { var templates = [ @@ -29,16 +31,8 @@ if (err) { done(err); } else { - try { - var statement = JSON.parse(res.body); - if (statement.authority) { - done(); - } else { - done(new Error('Statement "authority" has not been set.')); - } - } catch (error) { - done(error); - } + var statement = parse(res.body, done); + expect(statement).to.have.property('authority'); } }); }); @@ -105,21 +99,16 @@ .end() .get(helper.getEndpointStatements() + '?statementId=' + data.id) .headers(helper.addHeaderXapiVersion({})) - .expect(200).end(function (err, res) { + .expect(200) + .end(function (err, res) { if (err) { done(err); } else { - try { - var statement = JSON.parse(res.body); - var contextType = statement.context.contextActivities[type]; - if (Array.isArray(contextType)) { - done(); - } else { - done(new Error('Statement "' + type + '" not an array.')); - } - } catch (error) { - done(error); - } + var statement = parse(res.body, done); + expect(statement).to.have.property('context').to.have.property('contextActivities'); + expect(statement.context.contextActivities).to.have.property(type); + expect(statement.context.contextActivities[type]).to.be.an('array'); + done(); } }); }); @@ -144,21 +133,16 @@ .end() .get(helper.getEndpointStatements() + '?statementId=' + data.id) .headers(helper.addHeaderXapiVersion({})) - .expect(200).end(function (err, res) { + .expect(200) + .end(function (err, res) { if (err) { done(err); } else { - try { - var statement = JSON.parse(res.body); - var contextType = statement.object.context.contextActivities[type]; - if (Array.isArray(contextType)) { - done(); - } else { - done(new Error('Statement substatement "' + type + '" not an array.')); - } - } catch (error) { - done(error); - } + var statement = parse(res.body, done); + expect(statement).to.have.property('object').to.have.property('context').to.have.property('contextActivities'); + expect(statement.object.context.contextActivities).to.have.property(type); + expect(statement.object.context.contextActivities[type]).to.be.an('array'); + done(); } }); }); @@ -228,7 +212,7 @@ }); describe('An LRS rejects with error code 400 Bad Request, a PUT or POST Request which uses Attachments, has a "Content-Type" header with value "application/json", and has a discrepancy in the number of Attachments vs. the number of fileURL members (4.1.11.a)', function () { - it('should succeed when attachment uses "fileUrl" and request content-type is "application/json"', function (done) { + it('should fail when passing statement attachments and missing attachment"s binary', function (done) { var templates = [ {statement: '{{statements.attachment}}'}, { @@ -418,7 +402,7 @@ }); describe('An LRS modifies the value of the header of any Statement not rejected by the previous three requirements to "1.0.1" (4.1.10.b)', function () { - it('should set "version" to "1.0.1" when not provided by client', function (done) { + it('should respond with header "version" set to "1.0.1"', function (done) { var templates = [ {statement: '{{statements.default}}'} ]; @@ -434,22 +418,8 @@ .end() .get(helper.getEndpointStatements() + '?statementId=' + data.id) .headers(helper.addHeaderXapiVersion({})) - .expect(200).end(function (err, res) { - if (err) { - done(err); - } else { - try { - var statement = JSON.parse(res.body); - if (statement.version === "1.0.1") { - done(); - } else { - done(new Error('Statement "version" has not been set.')); - } - } catch (error) { - done(error); - } - } - }); + .expect(200) + .expect('x-experience-api-version', '1.0.1', done); }); }); @@ -474,18 +444,11 @@ if (err) { done(err); } else { - try { - var statement = JSON.parse(res.body); - if (helper.isEqual(data.actor, statement.actor) - && helper.isEqual(data.object, statement.object) - && helper.isEqual(data.verb, statement.verb)) { - done(); - } else { - done(new Error('Statement from LRS not same as statement sent.')); - } - } catch (error) { - done(error); - } + var statement = parse(res.body, done); + expect(helper.isEqual(data.actor, statement.actor)).to.be.true; + expect(helper.isEqual(data.object, statement.object)).to.be.true; + expect(helper.isEqual(data.verb, statement.verb)).to.be.true; + done(); } }); }); @@ -638,7 +601,7 @@ it('should fail with activities "DELETE"', function (done) { var query = qs.stringify({activityId: 'http://www.example.com/meetings/occurances/34534'}); requestPromise(helper.getEndpoint()) - .delete(helper.getEndpointActivitiesProfile() + '?' + query) + .delete(helper.getEndpointActivities() + '?' + query) .set('X-Experience-API-Version', '1.0.1') .expect(405, done); }); @@ -646,7 +609,7 @@ it('should fail with activities "POST"', function (done) { var query = qs.stringify({activityId: 'http://www.example.com/meetings/occurances/34534'}); request(helper.getEndpoint()) - .post(helper.getEndpointActivitiesProfile() + '?' + query) + .post(helper.getEndpointActivities() + '?' + query) .headers(helper.addHeaderXapiVersion({})) .expect(405, done); }); @@ -654,7 +617,7 @@ it('should fail with activities "PUT"', function (done) { var query = qs.stringify({activityId: 'http://www.example.com/meetings/occurances/34534'}); request(helper.getEndpoint()) - .put(helper.getEndpointActivitiesProfile() + '?' + query) + .put(helper.getEndpointActivities() + '?' + query) .headers(helper.addHeaderXapiVersion({})) .expect(405, done); }); @@ -874,16 +837,9 @@ if (err) { done(err); } else { - try { - var statement = JSON.parse(res.body); - if (statement.verb.id = data.verb.id) { - done(); - } else { - done(new Error('Statement "verb" should not be updated.')); - } - } catch (error) { - done(error); - } + var statement = parse(res.body, done); + expect(statement.verb.id).to.equal(data.verb.id); + done(); } }); }); @@ -916,16 +872,9 @@ if (err) { done(err); } else { - try { - var statement = JSON.parse(res.body); - if (statement.verb.id === data.verb.id) { - done(); - } else { - done(new Error('Statement "verb" should not be updated.')); - } - } catch (error) { - done(error); - } + var statement = parse(res.body, done); + expect(statement.verb.id).to.equal(data.verb.id); + done(); } }); }); @@ -972,7 +921,7 @@ } else if (res.statusCode === 409 || res.statusCode === 204) { done(); } else { - done(new Error('Missing no update status code using POST')) + done(new Error('Missing: no update status code using POST')) } }); }); @@ -1000,7 +949,7 @@ } else if (res.statusCode === 409 || res.statusCode === 204) { done(); } else { - done(new Error('Missing no update status code using POST')) + done(new Error('Missing: no update status code using PUT')) } }); }); @@ -1033,16 +982,9 @@ if (err) { done(err) } else { - try { - var result = JSON.parse(res.body); - if (Array.isArray(result.statements) && result.statements.length === 0) { - done(); - } else { - done(new Error('Statement "GET" result is not empty.')); - } - } catch (error) { - done(error); - } + var result = parse(res.body, done); + expect(result).to.have.property('statements').to.be.an('array').to.be.length(0); + done(); } }); }); @@ -1066,16 +1008,8 @@ if (err) { done(err) } else { - try { - var ids = res.body; - if (Array.isArray(ids) && ids.length > 0) { - done(); - } else { - done(new Error('Statement "POST" is an empty array.')); - } - } catch (error) { - done(error); - } + expect(res.body).to.be.an('array').to.have.length.above(0); + done(); } }); }); @@ -1102,16 +1036,10 @@ if (err) { done(err); } else { - try { - var result = JSON.parse(res.body); - if (result.more && validUrl.isUri(result.more)) { - done(); - } else { - done(new Error('Statement GET "more" is missing or not IRL.')); - } - } catch (error) { - done(error); - } + var result = parse(res.body, done); + expect(result).to.have.property('more'); + expect(validUrl.isUri(result.more)).to.be.truthy; + done(); } }); }); @@ -1128,16 +1056,9 @@ if (err) { done(err); } else { - try { - var more = JSON.parse(res.body).more; - if (more === '') { - done(); - } else { - done(new Error('Statement GET "more" is missing or not an empty string.')); - } - } catch (error) { - done(error); - } + var result = parse(res.body, done); + expect(result).to.have.property('more').to.be.truthy; + done(); } }); }); @@ -1153,16 +1074,10 @@ if (err) { done(err); } else { - try { - var result = JSON.parse(res.body); - if (result.more && validUrl.isUri(result.more)) { - done(); - } else { - done(new Error('Statement GET "more" is missing or not IRL.')); - } - } catch (error) { - done(error); - } + var result = parse(res.body, done); + expect(result).to.have.property('more'); + expect(validUrl.isUri(result.more)).to.be.truthy; + done(); } }); }); @@ -1212,16 +1127,9 @@ if (err) { done(err); } else { - try { - var statement = JSON.parse(res.body); - if (statement.id === voidedId) { - done(); - } else { - done(new Error('Statement "voidedStatementId" was not returned.')); - } - } catch (error) { - done(error); - } + var statement = parse(res.body, done); + expect(statement.id).to.equal(voidedId); + done(); } }); }); @@ -1266,23 +1174,7 @@ request(helper.getEndpoint()) .get(helper.getEndpointStatements() + '?' + query) .headers(helper.addHeaderXapiVersion({})) - .expect(200) - .end(function (err, res) { - if (err) { - done(err); - } else { - try { - var statement = JSON.parse(res.body); - if (statement.id) { - done(new Error('Statement "statementId" should not be returned.')); - } else { - done(); - } - } catch (error) { - done(error); - } - } - }); + .expect(404, done); }); }); @@ -1671,7 +1563,7 @@ .get(helper.getEndpointStatements() + '?' + query) .headers(helper.addHeaderXapiVersion({})) .expect(200, done); - }); + }); }); describe('An LRS\'s Statement API can process a GET request with "limit" as a parameter **Implicit**', function () { @@ -1734,8 +1626,7 @@ before('persist voiding statement', function (done) { var templates = [ - {statement: '{{statements.object_statementref}}'}, - {verb: '{{verbs.voided}}'} + {statement: '{{statements.voided}}'} ]; var voiding = createFromTemplate(templates); voiding = voiding.statement; @@ -1881,7 +1772,7 @@ it('should pass when using "voidedStatementId" with "format"', function (done) { var data = { - statementId: voidedId, + voidedStatementId: voidedId, format: 'ids' }; @@ -1894,7 +1785,7 @@ it('should pass when using "voidedStatementId" with "attachments"', function (done) { var data = { - statementId: voidedId, + voidedStatementId: voidedId, attachments: true }; @@ -1933,16 +1824,9 @@ if (err) { done(err); } else { - try { - var statement = JSON.parse(res.body); - if (statement.id === id) { - done(); - } else { - done(new Error('Statement "id" does not match requested statementId.')); - } - } catch (error) { - done(error); - } + var statement = parse(res.body, done); + expect(statement.id).to.equal(id); + done(); } }); }); @@ -1992,16 +1876,9 @@ if (err) { done(err); } else { - try { - var statement = JSON.parse(res.body); - if (statement.id === voidedId) { - done(); - } else { - done(new Error('Statement "id" does not match requested voidedStatementId.')); - } - } catch (error) { - done(error); - } + var statement = parse(res.body, done); + expect(statement.id).to.equal(voidedId); + done(); } }); }); @@ -2063,16 +1940,9 @@ if (err) { done(err); } else { - try { - var result = JSON.parse(res.body); - if (result.statements && Array.isArray(result.statements)) { - done(); - } else { - done(new Error('Statement "GET" does not return StatementResult.')); - } - } catch (error) { - done(error); - } + var result = parse(res.body, done); + expect(result).to.have.property('statements').to.be.an('array'); + done(); } }); }); @@ -2092,16 +1962,9 @@ if (err) { done(err); } else { - try { - var result = JSON.parse(res.body); - if (result.statements && Array.isArray(result.statements)) { - done(); - } else { - done(new Error('Statement "GET" does not return StatementResult.')); - } - } catch (error) { - done(error); - } + var result = parse(res.body, done); + expect(result).to.have.property('statements').to.be.an('array'); + done(); } }); }); @@ -2116,16 +1979,9 @@ if (err) { done(err); } else { - try { - var result = JSON.parse(res.body); - if (result.statements && Array.isArray(result.statements)) { - done(); - } else { - done(new Error('Statement "GET" does not return StatementResult.')); - } - } catch (error) { - done(error); - } + var result = parse(res.body, done); + expect(result).to.have.property('statements').to.be.an('array'); + done(); } }); }); @@ -2140,16 +1996,9 @@ if (err) { done(err); } else { - try { - var result = JSON.parse(res.body); - if (result.statements && Array.isArray(result.statements)) { - done(); - } else { - done(new Error('Statement "GET" does not return StatementResult.')); - } - } catch (error) { - done(error); - } + var result = parse(res.body, done); + expect(result).to.have.property('statements').to.be.an('array'); + done(); } }); }); @@ -2164,16 +2013,9 @@ if (err) { done(err); } else { - try { - var result = JSON.parse(res.body); - if (result.statements && Array.isArray(result.statements)) { - done(); - } else { - done(new Error('Statement "GET" does not return StatementResult.')); - } - } catch (error) { - done(error); - } + var result = parse(res.body, done); + expect(result).to.have.property('statements').to.be.an('array'); + done(); } }); }); @@ -2191,16 +2033,9 @@ if (err) { done(err); } else { - try { - var result = JSON.parse(res.body); - if (result.statements && Array.isArray(result.statements)) { - done(); - } else { - done(new Error('Statement "GET" does not return StatementResult.')); - } - } catch (error) { - done(error); - } + var result = parse(res.body, done); + expect(result).to.have.property('statements').to.be.an('array'); + done(); } }); }); @@ -2218,16 +2053,9 @@ if (err) { done(err); } else { - try { - var result = JSON.parse(res.body); - if (result.statements && Array.isArray(result.statements)) { - done(); - } else { - done(new Error('Statement "GET" does not return StatementResult.')); - } - } catch (error) { - done(error); - } + var result = parse(res.body, done); + expect(result).to.have.property('statements').to.be.an('array'); + done(); } }); }); @@ -2242,16 +2070,9 @@ if (err) { done(err); } else { - try { - var result = JSON.parse(res.body); - if (result.statements && Array.isArray(result.statements)) { - done(); - } else { - done(new Error('Statement "GET" does not return StatementResult.')); - } - } catch (error) { - done(error); - } + var result = parse(res.body, done); + expect(result).to.have.property('statements').to.be.an('array'); + done(); } }); }); @@ -2266,16 +2087,9 @@ if (err) { done(err); } else { - try { - var result = JSON.parse(res.body); - if (result.statements && Array.isArray(result.statements)) { - done(); - } else { - done(new Error('Statement "GET" does not return StatementResult.')); - } - } catch (error) { - done(error); - } + var result = parse(res.body, done); + expect(result).to.have.property('statements').to.be.an('array'); + done(); } }); }); @@ -2290,16 +2104,9 @@ if (err) { done(err); } else { - try { - var result = JSON.parse(res.body); - if (result.statements && Array.isArray(result.statements)) { - done(); - } else { - done(new Error('Statement "GET" does not return StatementResult.')); - } - } catch (error) { - done(error); - } + var result = parse(res.body, done); + expect(result).to.have.property('statements').to.be.an('array'); + done(); } }); }); @@ -2314,16 +2121,9 @@ if (err) { done(err); } else { - try { - var result = JSON.parse(res.body); - if (result.statements && Array.isArray(result.statements)) { - done(); - } else { - done(new Error('Statement "GET" does not return StatementResult.')); - } - } catch (error) { - done(error); - } + var result = parse(res.body, done); + expect(result).to.have.property('statements').to.be.an('array'); + done(); } }); }); @@ -2338,16 +2138,9 @@ if (err) { done(err); } else { - try { - var result = JSON.parse(res.body); - if (result.statements && Array.isArray(result.statements)) { - done(); - } else { - done(new Error('Statement "GET" does not return StatementResult.')); - } - } catch (error) { - done(error); - } + var results = parse(res.body, done); + expect(results).to.have.property('statements'); + done(); } }); }); @@ -2362,16 +2155,13 @@ if (err) { done(err); } else { - try { - var result = JSON.parse(res.body); - if (result.statements && Array.isArray(result.statements)) { - done(); - } else { - done(new Error('Statement "GET" does not return StatementResult.')); - } - } catch (error) { - done(error); - } + var boundary = multipartParser.getBoundary(res.headers['content-type']); + expect(boundary).to.be.ok; + var parsed = multipartParser.parseMultipart(boundary, res.body); + expect(parsed).to.be.ok; + var results = parse(parsed[0].body, done); + expect(results).to.have.property('statements'); + done(); } }); }); @@ -2387,27 +2177,23 @@ if (err) { done(err); } else { - try { - var result = JSON.parse(res.body); - if (result.statements && Array.isArray(result.statements)) { - var through = moment(res.headers['X-Experience-API-Consistent-Through'], moment.ISO_8601); - - var statements = result.statements; - for (var i = 0; i < statements.length; i++) { - var stored = moment(statements[i].stored, moment.ISO_8601); - if (!through.isValid() || !stored.isValid()) { - done(new Error('Statement dates not valid.')); - } - if (through.isBefore(stored)) { - done(new Error('Statement "stored" date is after "X-Experience-API-Consistent-Through.')); - } - } - done(); - } else { - } - } catch (error) { - done(error); + var value = res.headers['x-experience-api-consistent-through']; + expect(value).to.be.ok; + var through = moment(value, moment.ISO_8601); + expect(through).to.be.ok; + + var results = parse(res.body, done); + expect(results).to.have.property('statements'); + + var statements = results.statements; + for (var i = 0; i < statements.length; i++) { + var statement = statements[i]; + expect(statement).to.have.property('stored'); + var stored = moment(statement.stored, moment.ISO_8601); + expect(stored.isValid()).to.be.true; + expect(stored.isBefore(through)).to.be.true; } + done(); } }); }); @@ -2423,16 +2209,9 @@ if (err) { done(err); } else { - try { - var through = res.headers['X-Experience-API-Consistent-Through']; - if (through) { - done(); - } else { - done(new Error('Statement "X-Experience-API-Consistent-Through" not in header.')); - } - } catch (error) { - done(error); - } + var through = res.headers['x-experience-api-consistent-through']; + expect(through).to.be.ok; + done(); } }); }); @@ -2452,16 +2231,9 @@ if (err) { done(err); } else { - try { - var through = res.headers['X-Experience-API-Consistent-Through']; - if (through) { - done(); - } else { - done(new Error('Statement "X-Experience-API-Consistent-Through" not in header.')); - } - } catch (error) { - done(error); - } + var through = res.headers['x-experience-api-consistent-through']; + expect(through).to.be.ok; + done(); } }); }); @@ -2476,16 +2248,9 @@ if (err) { done(err); } else { - try { - var through = res.headers['X-Experience-API-Consistent-Through']; - if (through) { - done(); - } else { - done(new Error('Statement "X-Experience-API-Consistent-Through" not in header.')); - } - } catch (error) { - done(error); - } + var through = res.headers['x-experience-api-consistent-through']; + expect(through).to.be.ok; + done(); } }); }); @@ -2500,16 +2265,9 @@ if (err) { done(err); } else { - try { - var through = res.headers['X-Experience-API-Consistent-Through']; - if (through) { - done(); - } else { - done(new Error('Statement "X-Experience-API-Consistent-Through" not in header.')); - } - } catch (error) { - done(error); - } + var through = res.headers['x-experience-api-consistent-through']; + expect(through).to.be.ok; + done(); } }); }); @@ -2524,16 +2282,9 @@ if (err) { done(err); } else { - try { - var through = res.headers['X-Experience-API-Consistent-Through']; - if (through) { - done(); - } else { - done(new Error('Statement "X-Experience-API-Consistent-Through" not in header.')); - } - } catch (error) { - done(error); - } + var through = res.headers['x-experience-api-consistent-through']; + expect(through).to.be.ok; + done(); } }); }); @@ -2548,16 +2299,9 @@ if (err) { done(err); } else { - try { - var through = res.headers['X-Experience-API-Consistent-Through']; - if (through) { - done(); - } else { - done(new Error('Statement "X-Experience-API-Consistent-Through" not in header.')); - } - } catch (error) { - done(error); - } + var through = res.headers['x-experience-api-consistent-through']; + expect(through).to.be.ok; + done(); } }); }); @@ -2572,16 +2316,9 @@ if (err) { done(err); } else { - try { - var through = res.headers['X-Experience-API-Consistent-Through']; - if (through) { - done(); - } else { - done(new Error('Statement "X-Experience-API-Consistent-Through" not in header.')); - } - } catch (error) { - done(error); - } + var through = res.headers['x-experience-api-consistent-through']; + expect(through).to.be.ok; + done(); } }); }); @@ -2596,16 +2333,9 @@ if (err) { done(err); } else { - try { - var through = res.headers['X-Experience-API-Consistent-Through']; - if (through) { - done(); - } else { - done(new Error('Statement "X-Experience-API-Consistent-Through" not in header.')); - } - } catch (error) { - done(error); - } + var through = res.headers['x-experience-api-consistent-through']; + expect(through).to.be.ok; + done(); } }); }); @@ -2620,16 +2350,9 @@ if (err) { done(err); } else { - try { - var through = res.headers['X-Experience-API-Consistent-Through']; - if (through) { - done(); - } else { - done(new Error('Statement "X-Experience-API-Consistent-Through" not in header.')); - } - } catch (error) { - done(error); - } + var through = res.headers['x-experience-api-consistent-through']; + expect(through).to.be.ok; + done(); } }); }); @@ -2644,16 +2367,9 @@ if (err) { done(err); } else { - try { - var through = res.headers['X-Experience-API-Consistent-Through']; - if (through) { - done(); - } else { - done(new Error('Statement "X-Experience-API-Consistent-Through" not in header.')); - } - } catch (error) { - done(error); - } + var through = res.headers['x-experience-api-consistent-through']; + expect(through).to.be.ok; + done(); } }); }); @@ -2668,16 +2384,9 @@ if (err) { done(err); } else { - try { - var through = res.headers['X-Experience-API-Consistent-Through']; - if (through) { - done(); - } else { - done(new Error('Statement "X-Experience-API-Consistent-Through" not in header.')); - } - } catch (error) { - done(error); - } + var through = res.headers['x-experience-api-consistent-through']; + expect(through).to.be.ok; + done(); } }); }); @@ -2692,16 +2401,9 @@ if (err) { done(err); } else { - try { - var through = res.headers['X-Experience-API-Consistent-Through']; - if (through) { - done(); - } else { - done(new Error('Statement "X-Experience-API-Consistent-Through" not in header.')); - } - } catch (error) { - done(error); - } + var through = res.headers['x-experience-api-consistent-through']; + expect(through).to.be.ok; + done(); } }); }); @@ -2716,16 +2418,9 @@ if (err) { done(err); } else { - try { - var through = res.headers['X-Experience-API-Consistent-Through']; - if (through) { - done(); - } else { - done(new Error('Statement "X-Experience-API-Consistent-Through" not in header.')); - } - } catch (error) { - done(error); - } + var through = res.headers['x-experience-api-consistent-through']; + expect(through).to.be.ok; + done(); } }); }); @@ -2764,16 +2459,12 @@ if (err) { done(err); } else { - try { - var through = moment(res.headers['X-Experience-API-Consistent-Through'], moment.ISO_8601); - if (!through.isValid()) { - done(); - } else { - done(new Error('Header "X-Experience-API-Consistent-Through" not valid.')); - } - } catch (error) { - done(error); - } + var value = res.headers['x-experience-api-consistent-through']; + expect(value).to.be.ok; + var through = moment(value, moment.ISO_8601); + expect(through).to.be.ok; + expect(through.isValid()).to.be.true; + done(); } }); }); @@ -2793,16 +2484,12 @@ if (err) { done(err); } else { - try { - var through = moment(res.headers['X-Experience-API-Consistent-Through'], moment.ISO_8601); - if (!through.isValid()) { - done(); - } else { - done(new Error('Header "X-Experience-API-Consistent-Through" not valid.')); - } - } catch (error) { - done(error); - } + var value = res.headers['x-experience-api-consistent-through']; + expect(value).to.be.ok; + var through = moment(value, moment.ISO_8601); + expect(through).to.be.ok; + expect(through.isValid()).to.be.true; + done(); } }); }); @@ -2817,16 +2504,12 @@ if (err) { done(err); } else { - try { - var through = moment(res.headers['X-Experience-API-Consistent-Through'], moment.ISO_8601); - if (!through.isValid()) { - done(); - } else { - done(new Error('Header "X-Experience-API-Consistent-Through" not valid.')); - } - } catch (error) { - done(error); - } + var value = res.headers['x-experience-api-consistent-through']; + expect(value).to.be.ok; + var through = moment(value, moment.ISO_8601); + expect(through).to.be.ok; + expect(through.isValid()).to.be.true; + done(); } }); }); @@ -2841,16 +2524,12 @@ if (err) { done(err); } else { - try { - var through = moment(res.headers['X-Experience-API-Consistent-Through'], moment.ISO_8601); - if (!through.isValid()) { - done(); - } else { - done(new Error('Header "X-Experience-API-Consistent-Through" not valid.')); - } - } catch (error) { - done(error); - } + var value = res.headers['x-experience-api-consistent-through']; + expect(value).to.be.ok; + var through = moment(value, moment.ISO_8601); + expect(through).to.be.ok; + expect(through.isValid()).to.be.true; + done(); } }); }); @@ -2865,16 +2544,12 @@ if (err) { done(err); } else { - try { - var through = moment(res.headers['X-Experience-API-Consistent-Through'], moment.ISO_8601); - if (!through.isValid()) { - done(); - } else { - done(new Error('Header "X-Experience-API-Consistent-Through" not valid.')); - } - } catch (error) { - done(error); - } + var value = res.headers['x-experience-api-consistent-through']; + expect(value).to.be.ok; + var through = moment(value, moment.ISO_8601); + expect(through).to.be.ok; + expect(through.isValid()).to.be.true; + done(); } }); }); @@ -2892,16 +2567,12 @@ if (err) { done(err); } else { - try { - var through = moment(res.headers['X-Experience-API-Consistent-Through'], moment.ISO_8601); - if (!through.isValid()) { - done(); - } else { - done(new Error('Header "X-Experience-API-Consistent-Through" not valid.')); - } - } catch (error) { - done(error); - } + var value = res.headers['x-experience-api-consistent-through']; + expect(value).to.be.ok; + var through = moment(value, moment.ISO_8601); + expect(through).to.be.ok; + expect(through.isValid()).to.be.true; + done(); } }); }); @@ -2919,16 +2590,12 @@ if (err) { done(err); } else { - try { - var through = moment(res.headers['X-Experience-API-Consistent-Through'], moment.ISO_8601); - if (!through.isValid()) { - done(); - } else { - done(new Error('Header "X-Experience-API-Consistent-Through" not valid.')); - } - } catch (error) { - done(error); - } + var value = res.headers['x-experience-api-consistent-through']; + expect(value).to.be.ok; + var through = moment(value, moment.ISO_8601); + expect(through).to.be.ok; + expect(through.isValid()).to.be.true; + done(); } }); }); @@ -2943,16 +2610,12 @@ if (err) { done(err); } else { - try { - var through = moment(res.headers['X-Experience-API-Consistent-Through'], moment.ISO_8601); - if (!through.isValid()) { - done(); - } else { - done(new Error('Header "X-Experience-API-Consistent-Through" not valid.')); - } - } catch (error) { - done(error); - } + var value = res.headers['x-experience-api-consistent-through']; + expect(value).to.be.ok; + var through = moment(value, moment.ISO_8601); + expect(through).to.be.ok; + expect(through.isValid()).to.be.true; + done(); } }); }); @@ -2967,16 +2630,12 @@ if (err) { done(err); } else { - try { - var through = moment(res.headers['X-Experience-API-Consistent-Through'], moment.ISO_8601); - if (!through.isValid()) { - done(); - } else { - done(new Error('Header "X-Experience-API-Consistent-Through" not valid.')); - } - } catch (error) { - done(error); - } + var value = res.headers['x-experience-api-consistent-through']; + expect(value).to.be.ok; + var through = moment(value, moment.ISO_8601); + expect(through).to.be.ok; + expect(through.isValid()).to.be.true; + done(); } }); }); @@ -2991,16 +2650,12 @@ if (err) { done(err); } else { - try { - var through = moment(res.headers['X-Experience-API-Consistent-Through'], moment.ISO_8601); - if (!through.isValid()) { - done(); - } else { - done(new Error('Header "X-Experience-API-Consistent-Through" not valid.')); - } - } catch (error) { - done(error); - } + var value = res.headers['x-experience-api-consistent-through']; + expect(value).to.be.ok; + var through = moment(value, moment.ISO_8601); + expect(through).to.be.ok; + expect(through.isValid()).to.be.true; + done(); } }); }); @@ -3015,16 +2670,12 @@ if (err) { done(err); } else { - try { - var through = moment(res.headers['X-Experience-API-Consistent-Through'], moment.ISO_8601); - if (!through.isValid()) { - done(); - } else { - done(new Error('Header "X-Experience-API-Consistent-Through" not valid.')); - } - } catch (error) { - done(error); - } + var value = res.headers['x-experience-api-consistent-through']; + expect(value).to.be.ok; + var through = moment(value, moment.ISO_8601); + expect(through).to.be.ok; + expect(through.isValid()).to.be.true; + done(); } }); }); @@ -3039,16 +2690,12 @@ if (err) { done(err); } else { - try { - var through = moment(res.headers['X-Experience-API-Consistent-Through'], moment.ISO_8601); - if (!through.isValid()) { - done(); - } else { - done(new Error('Header "X-Experience-API-Consistent-Through" not valid.')); - } - } catch (error) { - done(error); - } + var value = res.headers['x-experience-api-consistent-through']; + expect(value).to.be.ok; + var through = moment(value, moment.ISO_8601); + expect(through).to.be.ok; + expect(through.isValid()).to.be.true; + done(); } }); }); @@ -3063,16 +2710,12 @@ if (err) { done(err); } else { - try { - var through = moment(res.headers['X-Experience-API-Consistent-Through'], moment.ISO_8601); - if (!through.isValid()) { - done(); - } else { - done(new Error('Header "X-Experience-API-Consistent-Through" not valid.')); - } - } catch (error) { - done(error); - } + var value = res.headers['x-experience-api-consistent-through']; + expect(value).to.be.ok; + var through = moment(value, moment.ISO_8601); + expect(through).to.be.ok; + expect(through.isValid()).to.be.true; + done(); } }); }); @@ -3134,16 +2777,9 @@ if (err) { done(err); } else { - try { - var result = JSON.parse(res.body); - if (result.statements && Array.isArray(result.statements)) { - done(); - } else { - done(new Error('Statement "GET" does not return StatementResult.')); - } - } catch (error) { - done(error); - } + var result = parse(res.body, done); + expect(result).to.have.property('statements').to.be.an('array'); + done(); } }); }); @@ -3163,16 +2799,9 @@ if (err) { done(err); } else { - try { - var result = JSON.parse(res.body); - if (result.statements && Array.isArray(result.statements)) { - done(); - } else { - done(new Error('Statement "GET" does not return StatementResult.')); - } - } catch (error) { - done(error); - } + var result = parse(res.body, done); + expect(result).to.have.property('statements').to.be.an('array'); + done(); } }); }); @@ -3187,16 +2816,9 @@ if (err) { done(err); } else { - try { - var result = JSON.parse(res.body); - if (result.statements && Array.isArray(result.statements)) { - done(); - } else { - done(new Error('Statement "GET" does not return StatementResult.')); - } - } catch (error) { - done(error); - } + var result = parse(res.body, done); + expect(result).to.have.property('statements').to.be.an('array'); + done(); } }); }); @@ -3211,16 +2833,9 @@ if (err) { done(err); } else { - try { - var result = JSON.parse(res.body); - if (result.statements && Array.isArray(result.statements)) { - done(); - } else { - done(new Error('Statement "GET" does not return StatementResult.')); - } - } catch (error) { - done(error); - } + var result = parse(res.body, done); + expect(result).to.have.property('statements').to.be.an('array'); + done(); } }); }); @@ -3235,16 +2850,9 @@ if (err) { done(err); } else { - try { - var result = JSON.parse(res.body); - if (result.statements && Array.isArray(result.statements)) { - done(); - } else { - done(new Error('Statement "GET" does not return StatementResult.')); - } - } catch (error) { - done(error); - } + var result = parse(res.body, done); + expect(result).to.have.property('statements').to.be.an('array'); + done(); } }); }); @@ -3262,16 +2870,9 @@ if (err) { done(err); } else { - try { - var result = JSON.parse(res.body); - if (Array.isArray(result.statements)) { - done(); - } else { - done(new Error('Statement "GET" does not return StatementResult.')); - } - } catch (error) { - done(error); - } + var result = parse(res.body, done); + expect(result).to.have.property('statements').to.be.an('array'); + done(); } }); }); @@ -3289,16 +2890,9 @@ if (err) { done(err); } else { - try { - var result = JSON.parse(res.body); - if (result.statements && Array.isArray(result.statements)) { - done(); - } else { - done(new Error('Statement "GET" does not return StatementResult.')); - } - } catch (error) { - done(error); - } + var result = parse(res.body, done); + expect(result).to.have.property('statements').to.be.an('array'); + done(); } }); }); @@ -3313,16 +2907,9 @@ if (err) { done(err); } else { - try { - var result = JSON.parse(res.body); - if (result.statements && Array.isArray(result.statements)) { - done(); - } else { - done(new Error('Statement "GET" does not return StatementResult.')); - } - } catch (error) { - done(error); - } + var result = parse(res.body, done); + expect(result).to.have.property('statements').to.be.an('array'); + done(); } }); }); @@ -3337,16 +2924,9 @@ if (err) { done(err); } else { - try { - var result = JSON.parse(res.body); - if (result.statements && Array.isArray(result.statements)) { - done(); - } else { - done(new Error('Statement "GET" does not return StatementResult.')); - } - } catch (error) { - done(error); - } + var result = parse(res.body, done); + expect(result).to.have.property('statements').to.be.an('array'); + done(); } }); }); @@ -3361,16 +2941,9 @@ if (err) { done(err); } else { - try { - var result = JSON.parse(res.body); - if (result.statements && Array.isArray(result.statements)) { - done(); - } else { - done(new Error('Statement "GET" does not return StatementResult.')); - } - } catch (error) { - done(error); - } + var result = parse(res.body, done); + expect(result).to.have.property('statements').to.be.an('array'); + done(); } }); }); @@ -3385,16 +2958,9 @@ if (err) { done(err); } else { - try { - var result = JSON.parse(res.body); - if (result.statements && Array.isArray(result.statements)) { - done(); - } else { - done(new Error('Statement "GET" does not return StatementResult.')); - } - } catch (error) { - done(error); - } + var result = parse(res.body, done); + expect(result).to.have.property('statements').to.be.an('array'); + done(); } }); }); @@ -3409,16 +2975,9 @@ if (err) { done(err); } else { - try { - var result = JSON.parse(res.body); - if (result.statements && Array.isArray(result.statements)) { - done(); - } else { - done(new Error('Statement "GET" does not return StatementResult.')); - } - } catch (error) { - done(error); - } + var result = parse(res.body, done); + expect(result).to.have.property('statements').to.be.an('array'); + done(); } }); }); @@ -3426,58 +2985,38 @@ it('should return StatementResult with statements as array using GET with "attachments"', function (done) { var header = {'Content-Type': 'multipart/mixed; boundary=-------314159265358979323846'}; var attachment = fs.readFileSync('test/v1_0_2/templates/attachments/basic_text_multipart_attachment_valid.part', {encoding: 'binary'}); + var query = qs.stringify({attachments: true}); request(helper.getEndpoint()) .post(helper.getEndpointStatements()) .headers(helper.addHeaderXapiVersion(header)) - .body(attachment).expect(200).end(function (err, res) { + .body(attachment) + .expect(200) + .end() + .get(helper.getEndpointStatements() + '?' + query) + .headers(helper.addHeaderXapiVersion({})) + .expect(200) + .end(function (err, res) { if (err) { done(err); } else { - try { - var result = JSON.parse(res.body); - if (Array.isArray(result) && result.length > 0) { - var query = qs.stringify({ statementId: result[0], attachments: true}); - - request(helper.getEndpoint()) - .get(helper.getEndpointStatements() + '?' + query) - .headers(helper.addHeaderXapiVersion({})) - .expect(200) - .end(function (err, res) { - if (err) { - done(err); - } else { - try { - var boundary = multipartParser.getBoundary(res.headers['content-type']); - if (boundary) { - var parsed = multipartParser.parseMultipart(boundary, res.body); - var result = JSON.parse(parsed[0].body); - if (result.statements && Array.isArray(result.statements)) { - done(); - } else { - done(new Error('Statement "GET" does not return StatementResult.')); - } - } else { - done(new Error('Statement "GET" does not contain boundary in content-type.')); - } - } catch (error) { - done(error); - } - } - }); - } else { - done(new Error('Statement "POST" did not return array of IDs.')); - } - } catch (error) { - done(error); - } + var boundary = multipartParser.getBoundary(res.headers['content-type']); + expect(boundary).to.be.ok; + var parsed = multipartParser.parseMultipart(boundary, res.body); + expect(parsed).to.be.ok; + var results = parse(parsed[0].body, done); + expect(results).to.have.property('statements'); + done(); } - }) + }); }); }); describe('An LRS\'s Statement API, upon processing a successful GET request wishing to return a Voided Statement still returns Statements which target it (7.2.4.b)', function () { + var verbTemplate = 'http://adlnet.gov/expapi/test/voided/target/'; + var verb = verbTemplate + helper.generateUUID(); var voidedId = helper.generateUUID(); + var voidingId = helper.generateUUID(); var statementRefId = helper.generateUUID(); before('persist voided statement', function (done) { @@ -3487,8 +3026,8 @@ var voided = createFromTemplate(voidedTemplates); voided = voided.statement; voided.id = voidedId; - voidedId = voided.id; - voided.verb.id = 'http://adlnet.gov/expapi/test/voided/target'; + voided.verb.id = verb; + voided.timestamp = '2005-01-01T19:09:13.245Z'; request(helper.getEndpoint()) .post(helper.getEndpointStatements()) @@ -3504,7 +3043,9 @@ ]; var voiding = createFromTemplate(voidingTemplates); voiding = voiding.statement; + voiding.id = voidingId; voiding.object.id = voidedId; + voiding.timestamp = '2015-01-01T19:09:13.245Z'; request(helper.getEndpoint()) .post(helper.getEndpointStatements()) @@ -3521,6 +3062,7 @@ var context = createFromTemplate(contextTemplates); context = context.statement; context.context.statement.id = voidedId; + context.timestamp = '2013-01-01T19:09:13.245Z'; request(helper.getEndpoint()) .post(helper.getEndpointStatements()) @@ -3538,6 +3080,7 @@ var substatementContext = createFromTemplate(substatementContextTemplates); substatementContext = substatementContext.statement; substatementContext.object.context.statement.id = voidedId; + substatementContext.timestamp = '2013-01-01T19:09:13.245Z'; request(helper.getEndpoint()) .post(helper.getEndpointStatements()) @@ -3554,6 +3097,7 @@ statementRef = statementRef.statement; statementRef.id = statementRefId; statementRef.object.id = voidedId; + statementRef.timestamp = '2010-01-01T19:09:13.245Z'; request(helper.getEndpoint()) .post(helper.getEndpointStatements()) @@ -3570,6 +3114,7 @@ var subStatementStatementRef = createFromTemplate(subStatementStatementRefTemplates); subStatementStatementRef = subStatementStatementRef.statement; subStatementStatementRef.object.object.id = voidedId; + subStatementStatementRef.timestamp = '2013-01-01T19:09:13.245Z'; request(helper.getEndpoint()) .post(helper.getEndpointStatements()) @@ -3578,10 +3123,10 @@ .expect(200, done); }); - it('should not return StatementRef when using "since"', function (done) { + it('should only return Object StatementRef when using "since"', function (done) { var query = qs.stringify({ - verb: 'http://adlnet.gov/expapi/test/voided/target', - since: '2010-01-01T19:09:13.245Z' + verb: verb, + since: '2011-01-01T19:09:13.245Z' }); request(helper.getEndpoint()) .get(helper.getEndpointStatements() + '?' + query) @@ -3591,24 +3136,19 @@ if (err) { done(err); } else { - try { - var results = JSON.parse(res.body); - if (Array.isArray(results.statements) && results.statements.length === 0) { - done(); - } else { - done(new Error('StatementRefs should not be returned when using "since".')); - } - } catch (error) { - done(error); - } + var results = parse(res.body, done); + expect(results).to.have.property('statements'); + expect(results.statements).to.have.length(1); + expect(results.statements[0]).to.have.property('id').to.equal(voidingId); + done(); } }); }); - it('should not return StatementRef when using "until"', function (done) { + it('should only return Object StatementRef when using "until"', function (done) { var query = qs.stringify({ - verb: 'http://adlnet.gov/expapi/test/voided/target', - until: '2050-01-01T19:09:13.245Z' + verb: verb, + until: '2014-01-01T19:09:13.245Z' }); request(helper.getEndpoint()) .get(helper.getEndpointStatements() + '?' + query) @@ -3618,24 +3158,19 @@ if (err) { done(err); } else { - try { - var results = JSON.parse(res.body); - if (Array.isArray(results.statements) && results.statements.length === 0) { - done(); - } else { - done(new Error('StatementRefs should not be returned when using "until".')); - } - } catch (error) { - done(error); - } + var results = parse(res.body, done); + expect(results).to.have.property('statements'); + expect(results.statements).to.have.length(1); + expect(results.statements[0]).to.have.property('id').to.equal(statementRefId); + done(); } }); }); - it('should not return StatementRef when using "limit"', function (done) { + it('should only return Object StatementRef when using "limit"', function (done) { var query = qs.stringify({ - verb: 'http://adlnet.gov/expapi/test/voided/target', - limit: 10 + verb: verb, + limit: 1 }); request(helper.getEndpoint()) .get(helper.getEndpointStatements() + '?' + query) @@ -3645,23 +3180,18 @@ if (err) { done(err); } else { - try { - var results = JSON.parse(res.body); - if (Array.isArray(results.statements) && results.statements.length === 0) { - done(); - } else { - done(new Error('StatementRefs should not be returned when using "limit".')); - } - } catch (error) { - done(error); - } + var results = parse(res.body, done); + expect(results).to.have.property('statements'); + expect(results.statements).to.have.length(1); + expect(results.statements[0]).to.have.property('id').to.equal(statementRefId); + done(); } }); }); it('should return StatementRef when not using "since", "until", "limit"', function (done) { var query = qs.stringify({ - verb: 'http://adlnet.gov/expapi/test/voided/target' + verb: verb }); request(helper.getEndpoint()) .get(helper.getEndpointStatements() + '?' + query) @@ -3671,29 +3201,12 @@ if (err) { done(err); } else { - try { - var results = JSON.parse(res.body); - if (Array.isArray(results.statements) && results.statements.length > 0) { - var statements = results.statements; - var found = false; - for (var i = 0; i < statements.length; i++) { - var result = statements[i]; - if (result.id === statementRefId) { - found = true; - break; - } - } - if (found) { - done(); - } else { - done(new Error('StatementRefs "id" was not found.')); - } - } else { - done(new Error('StatementRefs should be returned when not using "since", "until", "limit".')); - } - } catch (error) { - done(error); - } + var results = parse(res.body, done); + expect(results).to.have.property('statements'); + expect(results.statements).to.have.length(2); + expect(results.statements[0]).to.have.property('id').to.equal(statementRefId); + expect(results.statements[1]).to.have.property('id').to.equal(voidingId); + done(); } }); }); @@ -4206,4 +3719,15 @@ return mockObject; } -}(module, require('fs'), require('extend'), require('moment'), require('super-request'), require('supertest-as-promised'), require('qs'), require('should'), require('valid-url'), require('./../helper'), require('./../multipartParser'))); + function parse(string, done) { + var parsed; + try { + parsed = JSON.parse(string); + } catch (error) { + done(error); + } + return parsed; + } + +}(module, require('fs'), require('extend'), require('moment'), require('super-request'), require('supertest-as-promised'), require('qs'), require('chai'), require('valid-url'), require('./../helper'), require('./../multipartParser'))); + From b15094bde71aed03b74f66dc784d055e1115d0e4 Mon Sep 17 00:00:00 2001 From: Fredrick Whorton Date: Fri, 24 Apr 2015 15:18:36 -0400 Subject: [PATCH 5/6] Update test through to be before or same. --- test/v1_0_2/non_templating.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/v1_0_2/non_templating.js b/test/v1_0_2/non_templating.js index fb16dbf..a374d42 100644 --- a/test/v1_0_2/non_templating.js +++ b/test/v1_0_2/non_templating.js @@ -2191,7 +2191,7 @@ expect(statement).to.have.property('stored'); var stored = moment(statement.stored, moment.ISO_8601); expect(stored.isValid()).to.be.true; - expect(stored.isBefore(through)).to.be.true; + expect(stored.isBefore(through) || stored.isSame(through)).to.be.true; } done(); } From b06b1fa2134e2e83a31b54cd87adbc9d19d7197b Mon Sep 17 00:00:00 2001 From: Fredrick Whorton Date: Wed, 29 Apr 2015 15:06:58 -0400 Subject: [PATCH 6/6] Update non-templating tests. --- test/v1_0_2/configs/activities.js | 36 ++++++------- test/v1_0_2/configs/contexts.js | 48 ++++++++++++++--- test/v1_0_2/configs/results.js | 7 +-- test/v1_0_2/configs/statements.js | 2 +- test/v1_0_2/configs/substatements.js | 26 +++------- .../activities/choice_no_choices.json | 25 +++++++++ .../templates/activities/likert_no_scale.json | 25 +++++++++ .../activities/matching_no_source.json | 51 +++++++++++++++++++ .../activities/matching_no_target.json | 51 +++++++++++++++++++ .../activities/performance_no_steps.json | 25 +++++++++ .../activities/sequencing_no_choices.json | 25 +++++++++ 11 files changed, 272 insertions(+), 49 deletions(-) create mode 100644 test/v1_0_2/templates/activities/choice_no_choices.json create mode 100644 test/v1_0_2/templates/activities/likert_no_scale.json create mode 100644 test/v1_0_2/templates/activities/matching_no_source.json create mode 100644 test/v1_0_2/templates/activities/matching_no_target.json create mode 100644 test/v1_0_2/templates/activities/performance_no_steps.json create mode 100644 test/v1_0_2/templates/activities/sequencing_no_choices.json diff --git a/test/v1_0_2/configs/activities.js b/test/v1_0_2/configs/activities.js index 47563a5..4c65e68 100644 --- a/test/v1_0_2/configs/activities.js +++ b/test/v1_0_2/configs/activities.js @@ -11,7 +11,7 @@ // defines overwriting data var INVALID_IRI = 'abc://should.fail.com'; var INVALID_NUMERIC = 12345; - var INVALID_OBJECT = {key: 'value'}; + var INVALID_OBJECT = {test: 'value'}; var INVALID_STRING = 'should error'; var INVALID_INTERACTION_COMPONENT_ID = { 'id': INVALID_OBJECT, @@ -48,10 +48,12 @@ ]; var VALID_ACTIVITY = {id: 'http://www.example.com/meetings/occurances/34534'}; var VALID_EXTENSIONS = { - 'http://example.com/profiles/meetings/extension/location': 'X:\\meetings\\minutes\\examplemeeting.one', - 'http://example.com/profiles/meetings/extension/reporter': { - 'name': 'Thomas', - 'id': 'http://openid.com/342' + extensions: { + 'http://example.com/profiles/meetings/extension/location': 'X:\\meetings\\minutes\\examplemeeting.one', + 'http://example.com/profiles/meetings/extension/reporter': { + 'name': 'Thomas', + 'id': 'http://openid.com/342' + } } }; var VALID_INTERACTION_COMPONENT = { @@ -1621,7 +1623,7 @@ name: 'statement activity "choice choices" missing "id"', templates: [ {statement: '{{statements.object_activity}}'}, - {object: '{{activities.choice}}'}, + {object: '{{activities.choice_no_choices}}'}, {definition: {choices: [INVALID_INTERACTION_NO_ID]}} ], expect: [400] @@ -1630,7 +1632,7 @@ name: 'statement activity "likert scale" missing "id"', templates: [ {statement: '{{statements.object_activity}}'}, - {object: '{{activities.likert}}'}, + {object: '{{activities.likert_no_scale}}'}, {definition: {scale: [INVALID_INTERACTION_NO_ID]}} ], expect: [400] @@ -1639,7 +1641,7 @@ name: 'statement activity "matching source" missing "id"', templates: [ {statement: '{{statements.object_activity}}'}, - {object: '{{activities.matching}}'}, + {object: '{{activities.matching_no_source}}'}, {definition: {source: [INVALID_INTERACTION_NO_ID]}} ], expect: [400] @@ -1648,7 +1650,7 @@ name: 'statement activity "matching target" missing "id"', templates: [ {statement: '{{statements.object_activity}}'}, - {object: '{{activities.matching}}'}, + {object: '{{activities.matching_no_target}}'}, {definition: {target: [INVALID_INTERACTION_NO_ID]}} ], expect: [400] @@ -1657,7 +1659,7 @@ name: 'statement activity "performance steps" missing "id"', templates: [ {statement: '{{statements.object_activity}}'}, - {object: '{{activities.performance}}'}, + {object: '{{activities.performance_no_steps}}'}, {definition: {steps: [INVALID_INTERACTION_NO_ID]}} ], expect: [400] @@ -1666,7 +1668,7 @@ name: 'statement activity "sequencing choices" missing "id"', templates: [ {statement: '{{statements.object_activity}}'}, - {object: '{{activities.sequencing}}'}, + {object: '{{activities.sequencing_no_choices}}'}, {definition: {choices: [INVALID_INTERACTION_NO_ID]}} ], expect: [400] @@ -1676,7 +1678,7 @@ templates: [ {statement: '{{statements.object_substatement}}'}, {object: '{{substatements.activity}}'}, - {object: '{{activities.choice}}'}, + {object: '{{activities.choice_no_choices}}'}, {definition: {choices: [INVALID_INTERACTION_NO_ID]}} ], expect: [400] @@ -1686,7 +1688,7 @@ templates: [ {statement: '{{statements.object_substatement}}'}, {object: '{{substatements.activity}}'}, - {object: '{{activities.likert}}'}, + {object: '{{activities.likert_no_scale}}'}, {definition: {scale: [INVALID_INTERACTION_NO_ID]}} ], expect: [400] @@ -1696,7 +1698,7 @@ templates: [ {statement: '{{statements.object_substatement}}'}, {object: '{{substatements.activity}}'}, - {object: '{{activities.matching}}'}, + {object: '{{activities.matching_no_source}}'}, {definition: {source: [INVALID_INTERACTION_NO_ID]}} ], expect: [400] @@ -1706,7 +1708,7 @@ templates: [ {statement: '{{statements.object_substatement}}'}, {object: '{{substatements.activity}}'}, - {object: '{{activities.matching}}'}, + {object: '{{activities.matching_no_target}}'}, {definition: {target: [INVALID_INTERACTION_NO_ID]}} ], expect: [400] @@ -1716,7 +1718,7 @@ templates: [ {statement: '{{statements.object_substatement}}'}, {object: '{{substatements.activity}}'}, - {object: '{{activities.performance}}'}, + {object: '{{activities.performance_no_steps}}'}, {definition: {steps: [INVALID_INTERACTION_NO_ID]}} ], expect: [400] @@ -1726,7 +1728,7 @@ templates: [ {statement: '{{statements.object_substatement}}'}, {object: '{{substatements.activity}}'}, - {object: '{{activities.sequencing}}'}, + {object: '{{activities.sequencing_no_choices}}'}, {definition: {choices: [INVALID_INTERACTION_NO_ID]}} ], expect: [400] diff --git a/test/v1_0_2/configs/contexts.js b/test/v1_0_2/configs/contexts.js index 80b705e..fe64613 100644 --- a/test/v1_0_2/configs/contexts.js +++ b/test/v1_0_2/configs/contexts.js @@ -320,7 +320,11 @@ templates: [ {statement: '{{statements.context}}'}, {context: '{{contexts.parent}}'}, - {contextActivities: [VALID_ACTIVITY]} + { + contextActivities: { + parent: [VALID_ACTIVITY] + } + } ], expect: [200] }, @@ -329,7 +333,11 @@ templates: [ {statement: '{{statements.context}}'}, {context: '{{contexts.grouping}}'}, - {contextActivities: [VALID_ACTIVITY]} + { + contextActivities: { + grouping: [VALID_ACTIVITY] + } + } ], expect: [200] }, @@ -338,7 +346,11 @@ templates: [ {statement: '{{statements.context}}'}, {context: '{{contexts.category}}'}, - {contextActivities: [VALID_ACTIVITY]} + { + contextActivities: { + category: [VALID_ACTIVITY] + } + } ], expect: [200] }, @@ -347,7 +359,11 @@ templates: [ {statement: '{{statements.context}}'}, {context: '{{contexts.other}}'}, - {contextActivities: [VALID_ACTIVITY]} + { + contextActivities: { + other: [VALID_ACTIVITY] + } + } ], expect: [200] }, @@ -357,7 +373,11 @@ {statement: '{{statements.object_substatement}}'}, {object: '{{substatements.context}}'}, {context: '{{contexts.parent}}'}, - {contextActivities: [VALID_ACTIVITY]} + { + contextActivities: { + parent: [VALID_ACTIVITY] + } + } ], expect: [200] }, @@ -367,7 +387,11 @@ {statement: '{{statements.object_substatement}}'}, {object: '{{substatements.context}}'}, {context: '{{contexts.grouping}}'}, - {contextActivities: [VALID_ACTIVITY]} + { + contextActivities: { + grouping: [VALID_ACTIVITY] + } + } ], expect: [200] }, @@ -377,7 +401,11 @@ {statement: '{{statements.object_substatement}}'}, {object: '{{substatements.context}}'}, {context: '{{contexts.category}}'}, - {contextActivities: [VALID_ACTIVITY]} + { + contextActivities: { + category: [VALID_ACTIVITY] + } + } ], expect: [200] }, @@ -387,7 +415,11 @@ {statement: '{{statements.object_substatement}}'}, {object: '{{substatements.context}}'}, {context: '{{contexts.other}}'}, - {contextActivities: [VALID_ACTIVITY]} + { + contextActivities: { + other: [VALID_ACTIVITY] + } + } ], expect: [200] } diff --git a/test/v1_0_2/configs/results.js b/test/v1_0_2/configs/results.js index 6418d8d..af6d088 100644 --- a/test/v1_0_2/configs/results.js +++ b/test/v1_0_2/configs/results.js @@ -13,8 +13,9 @@ var INVALID_NUMERIC = 12345; var INVALID_OBJECT = {key: 'invalid'}; var INVALID_STRING = 'should fail'; - var VALID_DECIMAL_DIGITS = .6767676; var VALID_DURATION = 'PT1H0M0.1S'; + var VALID_DECIMAL_DIGITS = .6767676; + var VALID_MAX_DECIMAL_DIGITS = 100.6767676; // configures tests module.exports.config = function () { @@ -164,7 +165,7 @@ templates: [ {statement: '{{statements.result}}'}, {result: '{{results.default}}'}, - {score: {max: VALID_DECIMAL_DIGITS}} + {score: {max: VALID_MAX_DECIMAL_DIGITS}} ], expect: [200] }, @@ -174,7 +175,7 @@ {statement: '{{statements.object_substatement}}'}, {object: '{{substatements.result}}'}, {result: '{{results.default}}'}, - {score: {max: VALID_DECIMAL_DIGITS}} + {score: {max: VALID_MAX_DECIMAL_DIGITS}} ], expect: [200] } diff --git a/test/v1_0_2/configs/statements.js b/test/v1_0_2/configs/statements.js index a1ef6aa..073b398 100644 --- a/test/v1_0_2/configs/statements.js +++ b/test/v1_0_2/configs/statements.js @@ -224,7 +224,7 @@ name: 'statement context should fail on "null"', templates: [ {statement: '{{statements.context}}'}, - {context: '{{context.default}}'}, + {context: '{{contexts.default}}'}, {registration: null} ], expect: [400] diff --git a/test/v1_0_2/configs/substatements.js b/test/v1_0_2/configs/substatements.js index 779b7b2..2bce70e 100644 --- a/test/v1_0_2/configs/substatements.js +++ b/test/v1_0_2/configs/substatements.js @@ -119,7 +119,7 @@ templates: [ {statement: '{{statements.object_substatement}}'}, {object: '{{statements.object_substatement}}'}, - {object: '{{substatements.default}}'}, + {object: '{{statements.object_substatement_default}}'} ], expect: [400] } @@ -133,7 +133,7 @@ templates: [ {statement: '{{statements.object_substatement}}'}, {object: '{{substatements.default}}'}, - {id: 'fd41c918-b88b-4b20-a0a5-a4c32391aaa0'}, + {id: 'fd41c918-b88b-4b20-a0a5-a4c32391aaa0'} ], expect: [400] } @@ -147,7 +147,7 @@ templates: [ {statement: '{{statements.object_substatement}}'}, {object: '{{substatements.default}}'}, - {stored: '2013-05-18T05:32:34.804Z'}, + {stored: '2013-05-18T05:32:34.804Z'} ], expect: [400] } @@ -161,7 +161,7 @@ templates: [ {statement: '{{statements.object_substatement}}'}, {object: '{{substatements.default}}'}, - {version: '1.0.0'}, + {version: '1.0.0'} ], expect: [400] } @@ -171,25 +171,11 @@ name: 'A Sub-Statement cannot use the "authority" property (4.1.4.2.f)', config: [ { - name: 'substatement invalid with property "stored"', - templates: [ - {statement: '{{statements.object_substatement}}'}, - {object: '{{statements.authority}}'}, - {authority: '{{agents.default}}'}, - ], - expect: [400] - } - ] - }, - { - name: 'A Sub-Statement cannot use the "authority" property (4.1.12)', - config: [ - { - name: 'substatement invalid with property "stored"', + name: 'substatement invalid with property "authority"', templates: [ {statement: '{{statements.object_substatement}}'}, {object: '{{statements.authority}}'}, - {authority: '{{agents.default}}'}, + {authority: '{{agents.default}}'} ], expect: [400] } diff --git a/test/v1_0_2/templates/activities/choice_no_choices.json b/test/v1_0_2/templates/activities/choice_no_choices.json new file mode 100644 index 0000000..6684b08 --- /dev/null +++ b/test/v1_0_2/templates/activities/choice_no_choices.json @@ -0,0 +1,25 @@ +{ + "objectType": "Activity", + "id": "http://www.example.com/meetings/categories/teammeeting", + "definition": { + "name": { + "en": "Choice" + }, + "description": { + "en": "Which of these prototypes are available at the beta site?" + }, + "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "moreInfo": "http://virtualmeeting.example.com/345256", + "interactionType": "choice", + "correctResponsesPattern": [ + "golf[,]tetris" + ], + "extensions": { + "http://example.com/profiles/meetings/extension/location": "X:\\meetings\\minutes\\examplemeeting.one", + "http://example.com/profiles/meetings/extension/reporter": { + "name": "Thomas", + "id": "http://openid.com/342" + } + } + } +} \ No newline at end of file diff --git a/test/v1_0_2/templates/activities/likert_no_scale.json b/test/v1_0_2/templates/activities/likert_no_scale.json new file mode 100644 index 0000000..a145379 --- /dev/null +++ b/test/v1_0_2/templates/activities/likert_no_scale.json @@ -0,0 +1,25 @@ +{ + "objectType": "Activity", + "id": "http://www.example.com/meetings/categories/teammeeting", + "definition": { + "name": { + "en": "Likert" + }, + "description": { + "en": "How awesome is Experience API?" + }, + "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "moreInfo": "http://virtualmeeting.example.com/345256", + "interactionType": "likert", + "correctResponsesPattern": [ + "likert_3" + ], + "extensions": { + "http://example.com/profiles/meetings/extension/location": "X:\\meetings\\minutes\\examplemeeting.one", + "http://example.com/profiles/meetings/extension/reporter": { + "name": "Thomas", + "id": "http://openid.com/342" + } + } + } +} \ No newline at end of file diff --git a/test/v1_0_2/templates/activities/matching_no_source.json b/test/v1_0_2/templates/activities/matching_no_source.json new file mode 100644 index 0000000..909e9d3 --- /dev/null +++ b/test/v1_0_2/templates/activities/matching_no_source.json @@ -0,0 +1,51 @@ +{ + "objectType": "Activity", + "id": "http://www.example.com/meetings/categories/teammeeting", + "definition": { + "name": { + "en": "Matching" + }, + "description": { + "en": "Match these people to their kickball team:" + }, + "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "moreInfo": "http://virtualmeeting.example.com/345256", + "interactionType": "matching", + "correctResponsesPattern": [ + "ben[.]3[,]chris[.]2[,]troy[.]4[,]freddie[.]1" + ], + "target": [ + { + "id": "1", + "description": { + "en-US": "Swift Kick in the Grass" + } + }, + { + "id": "2", + "description": { + "en-US": "We got Runs" + } + }, + { + "id": "3", + "description": { + "en-US": "Duck" + } + }, + { + "id": "4", + "description": { + "en-US": "Van Delay Industries" + } + } + ], + "extensions": { + "http://example.com/profiles/meetings/extension/location": "X:\\meetings\\minutes\\examplemeeting.one", + "http://example.com/profiles/meetings/extension/reporter": { + "name": "Thomas", + "id": "http://openid.com/342" + } + } + } +} \ No newline at end of file diff --git a/test/v1_0_2/templates/activities/matching_no_target.json b/test/v1_0_2/templates/activities/matching_no_target.json new file mode 100644 index 0000000..929ddeb --- /dev/null +++ b/test/v1_0_2/templates/activities/matching_no_target.json @@ -0,0 +1,51 @@ +{ + "objectType": "Activity", + "id": "http://www.example.com/meetings/categories/teammeeting", + "definition": { + "name": { + "en": "Matching" + }, + "description": { + "en": "Match these people to their kickball team:" + }, + "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "moreInfo": "http://virtualmeeting.example.com/345256", + "interactionType": "matching", + "correctResponsesPattern": [ + "ben[.]3[,]chris[.]2[,]troy[.]4[,]freddie[.]1" + ], + "source": [ + { + "id": "ben", + "description": { + "en-US": "Ben" + } + }, + { + "id": "chris", + "description": { + "en-US": "Chris" + } + }, + { + "id": "troy", + "description": { + "en-US": "Troy" + } + }, + { + "id": "freddie", + "description": { + "en-US": "Freddie" + } + } + ], + "extensions": { + "http://example.com/profiles/meetings/extension/location": "X:\\meetings\\minutes\\examplemeeting.one", + "http://example.com/profiles/meetings/extension/reporter": { + "name": "Thomas", + "id": "http://openid.com/342" + } + } + } +} \ No newline at end of file diff --git a/test/v1_0_2/templates/activities/performance_no_steps.json b/test/v1_0_2/templates/activities/performance_no_steps.json new file mode 100644 index 0000000..e405f02 --- /dev/null +++ b/test/v1_0_2/templates/activities/performance_no_steps.json @@ -0,0 +1,25 @@ +{ + "objectType": "Activity", + "id": "http://www.example.com/meetings/categories/teammeeting", + "definition": { + "name": { + "en": "Performance" + }, + "description": { + "en": "This interaction measures performance over a day of RS sports:" + }, + "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "moreInfo": "http://virtualmeeting.example.com/345256", + "interactionType": "performance", + "correctResponsesPattern": [ + "pong[.]1:[,]dg[.]:10[,]lunch[.]" + ], + "extensions": { + "http://example.com/profiles/meetings/extension/location": "X:\\meetings\\minutes\\examplemeeting.one", + "http://example.com/profiles/meetings/extension/reporter": { + "name": "Thomas", + "id": "http://openid.com/342" + } + } + } +} \ No newline at end of file diff --git a/test/v1_0_2/templates/activities/sequencing_no_choices.json b/test/v1_0_2/templates/activities/sequencing_no_choices.json new file mode 100644 index 0000000..dd75088 --- /dev/null +++ b/test/v1_0_2/templates/activities/sequencing_no_choices.json @@ -0,0 +1,25 @@ +{ + "objectType": "Activity", + "id": "http://www.example.com/meetings/categories/teammeeting", + "definition": { + "name": { + "en": "Sequencing" + }, + "description": { + "en": "Order players by their pong ladder position:" + }, + "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "moreInfo": "http://virtualmeeting.example.com/345256", + "interactionType": "sequencing", + "correctResponsesPattern": [ + "tim[,]mike[,]ells[,]ben" + ], + "extensions": { + "http://example.com/profiles/meetings/extension/location": "X:\\meetings\\minutes\\examplemeeting.one", + "http://example.com/profiles/meetings/extension/reporter": { + "name": "Thomas", + "id": "http://openid.com/342" + } + } + } +} \ No newline at end of file