From f8724c6052164f24ebed7c10ded5b1ce28e029c8 Mon Sep 17 00:00:00 2001 From: andrewjones Date: Sun, 9 Jun 2019 15:09:12 -0400 Subject: [PATCH] Add handlebars based templater. --- config.json.example | 2 +- implementations/empty-report.json | 241 ++++++++++++++ implementations/generate.js | 135 +++----- implementations/handlebars.js | 53 +++ implementations/head.hbs | 92 ++++++ implementations/implementation.hbs | 56 ++++ implementations/index.html | 481 ++++++++++++++++++---------- implementations/node-report.json | 241 ++++++++++++++ implementations/report-template.hbs | 10 + implementations/report-template.js | 13 + implementations/report.hbs | 37 +++ implementations/report.json | 241 ++++++++++++++ implementations/test.json | 2 +- package.json | 7 +- test/ImplementationReporter.js | 74 +++++ test/assertions.js | 2 + test/mocha-setup.js | 8 + 17 files changed, 1446 insertions(+), 249 deletions(-) create mode 100644 implementations/empty-report.json create mode 100644 implementations/handlebars.js create mode 100644 implementations/head.hbs create mode 100644 implementations/implementation.hbs create mode 100644 implementations/node-report.json create mode 100644 implementations/report-template.hbs create mode 100644 implementations/report-template.js create mode 100644 implementations/report.hbs create mode 100644 implementations/report.json create mode 100644 test/ImplementationReporter.js create mode 100644 test/mocha-setup.js diff --git a/config.json.example b/config.json.example index 6cef8be..7c710f3 100644 --- a/config.json.example +++ b/config.json.example @@ -1,3 +1,3 @@ { - "generator": "../did-cli/did", + "generator": "../your/app" } diff --git a/implementations/empty-report.json b/implementations/empty-report.json new file mode 100644 index 0000000..efb9f54 --- /dev/null +++ b/implementations/empty-report.json @@ -0,0 +1,241 @@ +{ + "Identifier": [ + { + "fullTitle": "A DID method specification MUST further restrict the generic DID syntax by defining its own method-name and its own method-specific-id syntax.", + "optional": false, + "title": "A DID method specification MUST further restrict the generic DID syntax by defining its own method-name and its own method-specific-id syntax.", + "pending": false, + "state": "passed", + "duration": 2, + "speed": "fast", + "errors": "" + }, + { + "fullTitle": "A method-specific parameter name MUST be prefixed by the method name as defined by the method-name rule.", + "optional": false, + "title": "A method-specific parameter name MUST be prefixed by the method name as defined by the method-name rule.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "A generic DID path is identical to a URI path and MUST conform to the the path-abempty ABNF rule in [RFC3986].", + "optional": false, + "title": "A generic DID path is identical to a URI path and MUST conform to the the path-abempty ABNF rule in [RFC3986].", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "A generic DID query is identical to a URI query and MUST conform to the the query ABNF rule in [RFC3986].", + "optional": false, + "title": "A generic DID query is identical to a URI query and MUST conform to the the query ABNF rule in [RFC3986].", + "pending": false, + "state": "failed", + "duration": 1, + "errors": "Not Implemented" + }, + { + "fullTitle": "A generic DID fragment is identical to a URI fragment and MUST conform to the the fragment ABNF rule in [RFC3986]", + "optional": false, + "title": "A generic DID fragment is identical to a URI fragment and MUST conform to the the fragment ABNF rule in [RFC3986]", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "The did: scheme name MUST be lowercase.", + "optional": false, + "title": "The did: scheme name MUST be lowercase.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "(did) The method name MUST be lowercase.", + "optional": false, + "title": "(did) The method name MUST be lowercase.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + } + ], + "Document": [ + { + "fullTitle": "The value of the @context property MUST be one or more URIs, where the value of the first URI ishttps://www.w3.org/2019/did/v1.", + "optional": false, + "title": "The value of the @context property MUST be one or more URIs, where the value of the first URI ishttps://www.w3.org/2019/did/v1.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "If more than one URI is provided, the URIs MUST be interpreted as an ordered set.", + "optional": false, + "title": "If more than one URI is provided, the URIs MUST be interpreted as an ordered set.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "DID Documents MUST include the @context property.", + "optional": false, + "title": "DID Documents MUST include the @context property.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "A DID Document MUST have exactly one top-level context statement.", + "optional": false, + "title": "A DID Document MUST have exactly one top-level context statement.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "The key for this property (context) MUST be @context.", + "optional": false, + "title": "The key for this property (context) MUST be @context.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "The value of this key MUST be at least the URL for the generic DID context: https://www.w3.org/2019/did/v1.", + "optional": false, + "title": "The value of this key MUST be at least the URL for the generic DID context: https://www.w3.org/2019/did/v1.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "Method-specific contexts MUST NOT override the terms defined in the generic DID context.", + "optional": false, + "title": "Method-specific contexts MUST NOT override the terms defined in the generic DID context.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "A DID Document MUST have exactly one DID subject.", + "optional": false, + "title": "A DID Document MUST have exactly one DID subject.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "The key for this property MUST be id.", + "optional": false, + "title": "The key for this property MUST be id.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "The value of this key MUST be a valid DID.", + "optional": false, + "title": "The value of this key MUST be a valid DID.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "However, the fully resolved DID Document MUST contain a valid id property.", + "optional": false, + "title": "However, the fully resolved DID Document MUST contain a valid id property.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "A DID Document that contains a revoked key MUST also contain or refer to the revocation information for the key (e.g., a revocation list).", + "optional": false, + "title": "A DID Document that contains a revoked key MUST also contain or refer to the revocation information for the key (e.g., a revocation list).", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "The value of the publicKey property MUST be an array of public keys.", + "optional": false, + "title": "The value of the publicKey property MUST be an array of public keys.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "Each public key MUST include id and type properties, and exactly one value property. The array of public keys MUST NOT contain duplicate entries with the same id.", + "optional": false, + "title": "Each public key MUST include id and type properties, and exactly one value property. The array of public keys MUST NOT contain duplicate entries with the same id.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "Each public key MUST include a controller property, which identifies the controller of the corresponding private key.", + "optional": false, + "title": "Each public key MUST include a controller property, which identifies the controller of the corresponding private key.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "The value property of a public key MUST be exactly one of publicKeyPem, publicKeyJwk, publicKeyHex, publicKeyBase64, publicKeyBase58, publicKeyMultibase, depending on the format and encoding of the public key.", + "optional": false, + "title": "The value property of a public key MUST be exactly one of publicKeyPem, publicKeyJwk, publicKeyHex, publicKeyBase64, publicKeyBase58, publicKeyMultibase, depending on the format and encoding of the public key.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "Each service endpoint MUSTmust include id, type, and serviceEndpoint properties, and MAY include additional properties.", + "optional": false, + "title": "Each service endpoint MUSTmust include id, type, and serviceEndpoint properties, and MAY include additional properties.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "The value of the serviceEndpoint property MUST be a JSON-LD object or a valid URI conforming to [RFC3986] and normalized according to the rules in section 6 of [RFC3986] and to any normalization rules in its applicable URI scheme specification.", + "optional": false, + "title": "The value of the serviceEndpoint property MUST be a JSON-LD object or a valid URI conforming to [RFC3986] and normalized according to the rules in section 6 of [RFC3986] and to any normalization rules in its applicable URI scheme specification.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "A DID Document MUST be a single JSON object conforming to [RFC8259].", + "optional": false, + "title": "A DID Document MUST be a single JSON object conforming to [RFC8259].", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + } + ] +} diff --git a/implementations/generate.js b/implementations/generate.js index c51b2f2..167ee13 100644 --- a/implementations/generate.js +++ b/implementations/generate.js @@ -1,3 +1,8 @@ +/** + * Copyright (c) 2019 Digital Bazaar, Inc. All rights reserved. +*/ +'use strict'; + /** * Generates the HTTP Signatures Implementation Report given * a set of *-report.json files. @@ -5,13 +10,12 @@ const fs = require('fs'); const path = require('path'); const testConfig = require('./test.json'); +const template = require('./handlebars'); // extract the results from all of the test files -const allTests = []; -const allResults = {}; const dirContents = fs.readdirSync(__dirname); const files = dirContents.filter( - contents => {return contents.match(/.*-report.json/ig);}); + contents => contents.match(/.*-report.json/ig)); if(!files.length) { throw new Error( @@ -19,90 +23,57 @@ if(!files.length) { `in the dir ${__dirname}`); } -function getTestStatus(test, pendingTitles) { - if(pendingTitles.includes(test.fullTitle)) { - return 'skipped'; - } - const errorCount = Object.keys(test.err).length; - if(errorCount > 0) { - return 'failure'; +//masterName is the name of the main report +//used to get the title of all tests for the spec +function makeMaster(masterName) { + const context = { + implementations: [], + tests: {} + }; + const masterFile = `${masterName}-report.json`; + const masterPath = files.find(f => f === masterFile); + if(!masterPath) { + throw new Error(`Unable to find json report at ${masterPath} + for ${masterName}. This file is used to generate + the report and is set in test.json`); } - return 'success'; -} - -// process each test file -files.forEach(file => { - const implementation = file.match(/(.*)-report.json/)[1]; - const results = JSON.parse(fs.readFileSync( - path.join(__dirname, file)), 'utf-8'); - allResults[implementation] = {}; - const pendingTitles = results.pending.map(t => t.fullTitle); - - // process each test, noting the result - results.tests.forEach(test => { - allResults[implementation][test.fullTitle] = - getTestStatus(test, pendingTitles); - // assume vc.js tests all features - // TODO abstract this out - if(implementation === testConfig.implementation) { - allTests.push(test.fullTitle); - } + const masterJson = require(path.join(__dirname, masterFile)); + const testNames = Object.keys(masterJson); + testNames.forEach(name => { + context.tests[name] = {}; + masterJson[name].forEach(t => { + if(!t.pending) { + context.tests[name][t.fullTitle] = []; + } + }); }); -}); - -// generate the implementation report -const implementations = Object.keys(allResults).sort(); -let conformanceTable = ` - - - -`; -implementations.forEach(implementation => { - conformanceTable += ``; -}); -conformanceTable += ` - - -`; - -// process each test -allTests.forEach(test => { - conformanceTable += ` - - - `; - implementations.forEach(implementation => { - const status = (allResults[implementation][test]) || 'unimplemented'; - let statusMark = '-'; - - if(status === 'success') { - statusMark = '✓'; - } - if(status === 'failure') { - statusMark = '❌'; - } - if(status === 'skipped') { - //skipped tests get a pause button - statusMark = '⏸'; - } + return context; +} - conformanceTable += ` - - `; +function makeContext(masterName) { + const context = makeMaster(masterName); + const testNames = Object.keys(context.tests); + // process each test file + files.forEach(file => { + const implementation = file.match(/(?.*)-report.json/).groups.title; + context.implementations.push(implementation); + const results = require(path.join(__dirname, file)); + // valid tests are tests that are + // not pending and in the master implementation. + testNames.forEach(name => { + results[name].forEach(test => { + if(context.tests[name][test.fullTitle]) { + context.tests[name][test.fullTitle].push(test); + } + }); + }); }); - conformanceTable += ` - </tr> - `; -}); -conformanceTable += ` - </tbody> -</table> -`; + return context; +} +const context = makeContext(testConfig.implementation); +const output = template(context); // output the implementation report -const template = fs.readFileSync( - path.join(__dirname, 'template.html'), 'utf-8'); -fs.writeFileSync(path.join(__dirname, 'index.html'), - template.replace('%%%REPORTS%%%', conformanceTable)); +fs.writeFileSync(path.join(__dirname, 'index.html'), output); console.log('Generated new implementation report.'); diff --git a/implementations/handlebars.js b/implementations/handlebars.js new file mode 100644 index 0000000..a641940 --- /dev/null +++ b/implementations/handlebars.js @@ -0,0 +1,53 @@ +const Handlebars = require('handlebars'); +const fs = require('fs'); +const path = require('path'); + +const dirContents = fs.readdirSync(__dirname); + +const partials = dirContents.filter( + contents => contents.match(/.*.hbs/ig)); + +partials.forEach(file => { + const partial = fs.readFileSync( + path.join(__dirname, file), 'utf8'); + Handlebars.registerPartial(file, partial); +}); + +Handlebars.registerHelper('getStatusMark', state => { + let statusMark = '-'; + + if(state === 'passed') { + statusMark = '✓'; + } + if(state === 'failed') { + statusMark = '❌'; + } + return statusMark; +}); + +Handlebars.registerHelper( + 'getOptional', optional => optional ? 'optional' : 'not-optional'); + +Handlebars.registerHelper( + 'getOptionalTitle', + title => /optional/i.test(title) ? 'optional' : 'not-optional'); + +const template = Handlebars.template; +const templates = Handlebars.templates = Handlebars.templates || {}; +//this code was autogenerated by handlebars +templates['report-template.hbs'] = template( + { + compiler: [7, '>= 4.0.0'], + main: function(container, depth0, helpers, partials, data) { + let stack1; + return '<!DOCTYPE html>\n<html>\n <head>\n' + + ((stack1 = container.invokePartial( + partials['head.hbs'], depth0, {name: 'head.hbs', data, indent: ' ', helpers, partials, decorators: container.decorators})) != null ? stack1 : '') + + ' </head>\n <body>\n' + + ((stack1 = container.invokePartial( + partials['implementation.hbs'], depth0, {name: 'implementation.hbs', data, indent: ' ', helpers, partials, decorators: container.decorators})) != null ? stack1 : '') + + ((stack1 = container.invokePartial(partials['report.hbs'], depth0, {name: 'report.hbs', data, indent: ' ', helpers, partials, decorators: container.decorators})) != null ? stack1 : '') + + ' </body>\n</html>\n'; +}, usePartial: true, useData: true}); + +module.exports = templates['report-template.hbs']; diff --git a/implementations/head.hbs b/implementations/head.hbs new file mode 100644 index 0000000..5a7b08a --- /dev/null +++ b/implementations/head.hbs @@ -0,0 +1,92 @@ + <title>Decentralized Identifiers Implementation Report 1.0 + + + + + diff --git a/implementations/implementation.hbs b/implementations/implementation.hbs new file mode 100644 index 0000000..af759f0 --- /dev/null +++ b/implementations/implementation.hbs @@ -0,0 +1,56 @@ +
+

+This is the most recent implementation report for the +HTTP Signatures specification. +

+
+ +
+

+Comments regarding this document are welcome. Please file issues +directly on GitHub, +or send them to +public-vc-comments@w3.org +(subscribe, +archives). +

+ +
+ +
+

Introduction

+ +

+The purpose of this document is to demonstrate that there are multiple +interoperable implementations of HTTP Signatures that are capable of generating +output that is conformant to the latest version. +

+ +
+

Testing Methodology

+ +

+The testing framework for HTTP Signatures executes the +following process for every conformance statement in the + +Http Signatures specification : +

+ +
    +
  1. +Take an input HTTP Message that exercises the feature and feed it to a +developer provided HTTP Signatures binary. +
  2. +
  3. +If the input is valid, generate a HTTP Signature that is conformant +to options and HTTP message. +
  4. +
  5. +The test suite then ensures that the generated HTTP message is +conformant to the feature being tested. +
  6. +
+ +
+ +
diff --git a/implementations/index.html b/implementations/index.html index a576746..5f96490 100644 --- a/implementations/index.html +++ b/implementations/index.html @@ -1,174 +1,331 @@ - Decentralized Identifiers Implementation Report 1.0 - - - - - + Decentralized Identifiers Implementation Report 1.0 + + + + + -
-

-This is the most recent implementation report for the -Decentralized Identifiers specification. -

-
- -
-

-Comments regarding this document are welcome. Please file issues -directly on GitHub, -or send them to -public-vc-comments@w3.org -(subscribe, -archives). -

- -
- -
-

Introduction

- -

-The purpose of this document is to demonstrate that there are at least two -interoperable implementations of processors that are capable of generating -output that is conformant with the -Decentralized Identifiers spec. -

- -
-

Testing Methodology

- -

-The testing framework for the Decentralized Identifiers executes the -following process for every conformance statement in the - -Decentralized Identifiers spec.: -

- -
    -
  1. -Take an input file template that exercises the feature and feed it to a -developer provided DID generator. -
  2. -
  3. -If the input is valid, generate a DID that is conformant -to the spec. -
  4. -
  5. -The test suite then ensures that the generated DID is -conformant to the feature being tested. -
  6. -
- -
- -
- +
+

+ This is the most recent implementation report for the + HTTP Signatures specification. +

+
+ +
+

+ Comments regarding this document are welcome. Please file issues + directly on GitHub, + or send them to + public-vc-comments@w3.org + (subscribe, + archives). +

+ +
+ +
+

Introduction

+ +

+ The purpose of this document is to demonstrate that there are multiple + interoperable implementations of HTTP Signatures that are capable of generating + output that is conformant to the latest version. +

+ +
+

Testing Methodology

+ +

+ The testing framework for HTTP Signatures executes the + following process for every conformance statement in the + + Http Signatures specification : +

+ +
    +
  1. + Take an input HTTP Message that exercises the feature and feed it to a + developer provided HTTP Signatures binary. +
  2. +
  3. + If the input is valid, generate a HTTP Signature that is conformant + to options and HTTP message. +
  4. +
  5. + The test suite then ensures that the generated HTTP message is + conformant to the feature being tested. +
  6. +
+ +
+ +

Conformance Testing Results

-

-The results of the conformance testing are shown below: + The results of the conformance testing are shown below:

- - -
Test${implementation}
${test}${statusMark}
- - - - - - - -
Testfake-badfake-goodskipvc.js
- - +
+

Identifier

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Testemptynode
A DID method specification MUST further restrict the generic DID syntax by defining its own method-name and its own method-specific-id syntax.
A method-specific parameter name MUST be prefixed by the method name as defined by the method-name rule.
A generic DID path is identical to a URI path and MUST conform to the the path-abempty ABNF rule in [RFC3986].
A generic DID query is identical to a URI query and MUST conform to the the query ABNF rule in [RFC3986].
A generic DID fragment is identical to a URI fragment and MUST conform to the the fragment ABNF rule in [RFC3986]
The did: scheme name MUST be lowercase.
(did) The method name MUST be lowercase.
+
+
+

Document

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Testemptynode
The value of the @context property MUST be one or more URIs, where the value of the first URI ishttps://www.w3.org/2019/did/v1.
If more than one URI is provided, the URIs MUST be interpreted as an ordered set.
DID Documents MUST include the @context property.
A DID Document MUST have exactly one top-level context statement.
The key for this property (context) MUST be @context.
The value of this key MUST be at least the URL for the generic DID context: https://www.w3.org/2019/did/v1.
Method-specific contexts MUST NOT override the terms defined in the generic DID context.
A DID Document MUST have exactly one DID subject.
The key for this property MUST be id.
The value of this key MUST be a valid DID.
However, the fully resolved DID Document MUST contain a valid id property.
A DID Document that contains a revoked key MUST also contain or refer to the revocation information for the key (e.g., a revocation list).
The value of the publicKey property MUST be an array of public keys.
Each public key MUST include id and type properties, and exactly one value property. The array of public keys MUST NOT contain duplicate entries with the same id.
Each public key MUST include a controller property, which identifies the controller of the corresponding private key.
The value property of a public key MUST be exactly one of publicKeyPem, publicKeyJwk, publicKeyHex, publicKeyBase64, publicKeyBase58, publicKeyMultibase, depending on the format and encoding of the public key.
Each service endpoint MUSTmust include id, type, and serviceEndpoint properties, and MAY include additional properties.
The value of the serviceEndpoint property MUST be a JSON-LD object or a valid URI conforming to [RFC3986] and normalized according to the rules in section 6 of [RFC3986] and to any normalization rules in its applicable URI scheme specification.
A DID Document MUST be a single JSON object conforming to [RFC8259].
+
diff --git a/implementations/node-report.json b/implementations/node-report.json new file mode 100644 index 0000000..efb9f54 --- /dev/null +++ b/implementations/node-report.json @@ -0,0 +1,241 @@ +{ + "Identifier": [ + { + "fullTitle": "A DID method specification MUST further restrict the generic DID syntax by defining its own method-name and its own method-specific-id syntax.", + "optional": false, + "title": "A DID method specification MUST further restrict the generic DID syntax by defining its own method-name and its own method-specific-id syntax.", + "pending": false, + "state": "passed", + "duration": 2, + "speed": "fast", + "errors": "" + }, + { + "fullTitle": "A method-specific parameter name MUST be prefixed by the method name as defined by the method-name rule.", + "optional": false, + "title": "A method-specific parameter name MUST be prefixed by the method name as defined by the method-name rule.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "A generic DID path is identical to a URI path and MUST conform to the the path-abempty ABNF rule in [RFC3986].", + "optional": false, + "title": "A generic DID path is identical to a URI path and MUST conform to the the path-abempty ABNF rule in [RFC3986].", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "A generic DID query is identical to a URI query and MUST conform to the the query ABNF rule in [RFC3986].", + "optional": false, + "title": "A generic DID query is identical to a URI query and MUST conform to the the query ABNF rule in [RFC3986].", + "pending": false, + "state": "failed", + "duration": 1, + "errors": "Not Implemented" + }, + { + "fullTitle": "A generic DID fragment is identical to a URI fragment and MUST conform to the the fragment ABNF rule in [RFC3986]", + "optional": false, + "title": "A generic DID fragment is identical to a URI fragment and MUST conform to the the fragment ABNF rule in [RFC3986]", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "The did: scheme name MUST be lowercase.", + "optional": false, + "title": "The did: scheme name MUST be lowercase.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "(did) The method name MUST be lowercase.", + "optional": false, + "title": "(did) The method name MUST be lowercase.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + } + ], + "Document": [ + { + "fullTitle": "The value of the @context property MUST be one or more URIs, where the value of the first URI ishttps://www.w3.org/2019/did/v1.", + "optional": false, + "title": "The value of the @context property MUST be one or more URIs, where the value of the first URI ishttps://www.w3.org/2019/did/v1.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "If more than one URI is provided, the URIs MUST be interpreted as an ordered set.", + "optional": false, + "title": "If more than one URI is provided, the URIs MUST be interpreted as an ordered set.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "DID Documents MUST include the @context property.", + "optional": false, + "title": "DID Documents MUST include the @context property.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "A DID Document MUST have exactly one top-level context statement.", + "optional": false, + "title": "A DID Document MUST have exactly one top-level context statement.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "The key for this property (context) MUST be @context.", + "optional": false, + "title": "The key for this property (context) MUST be @context.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "The value of this key MUST be at least the URL for the generic DID context: https://www.w3.org/2019/did/v1.", + "optional": false, + "title": "The value of this key MUST be at least the URL for the generic DID context: https://www.w3.org/2019/did/v1.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "Method-specific contexts MUST NOT override the terms defined in the generic DID context.", + "optional": false, + "title": "Method-specific contexts MUST NOT override the terms defined in the generic DID context.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "A DID Document MUST have exactly one DID subject.", + "optional": false, + "title": "A DID Document MUST have exactly one DID subject.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "The key for this property MUST be id.", + "optional": false, + "title": "The key for this property MUST be id.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "The value of this key MUST be a valid DID.", + "optional": false, + "title": "The value of this key MUST be a valid DID.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "However, the fully resolved DID Document MUST contain a valid id property.", + "optional": false, + "title": "However, the fully resolved DID Document MUST contain a valid id property.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "A DID Document that contains a revoked key MUST also contain or refer to the revocation information for the key (e.g., a revocation list).", + "optional": false, + "title": "A DID Document that contains a revoked key MUST also contain or refer to the revocation information for the key (e.g., a revocation list).", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "The value of the publicKey property MUST be an array of public keys.", + "optional": false, + "title": "The value of the publicKey property MUST be an array of public keys.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "Each public key MUST include id and type properties, and exactly one value property. The array of public keys MUST NOT contain duplicate entries with the same id.", + "optional": false, + "title": "Each public key MUST include id and type properties, and exactly one value property. The array of public keys MUST NOT contain duplicate entries with the same id.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "Each public key MUST include a controller property, which identifies the controller of the corresponding private key.", + "optional": false, + "title": "Each public key MUST include a controller property, which identifies the controller of the corresponding private key.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "The value property of a public key MUST be exactly one of publicKeyPem, publicKeyJwk, publicKeyHex, publicKeyBase64, publicKeyBase58, publicKeyMultibase, depending on the format and encoding of the public key.", + "optional": false, + "title": "The value property of a public key MUST be exactly one of publicKeyPem, publicKeyJwk, publicKeyHex, publicKeyBase64, publicKeyBase58, publicKeyMultibase, depending on the format and encoding of the public key.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "Each service endpoint MUSTmust include id, type, and serviceEndpoint properties, and MAY include additional properties.", + "optional": false, + "title": "Each service endpoint MUSTmust include id, type, and serviceEndpoint properties, and MAY include additional properties.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "The value of the serviceEndpoint property MUST be a JSON-LD object or a valid URI conforming to [RFC3986] and normalized according to the rules in section 6 of [RFC3986] and to any normalization rules in its applicable URI scheme specification.", + "optional": false, + "title": "The value of the serviceEndpoint property MUST be a JSON-LD object or a valid URI conforming to [RFC3986] and normalized according to the rules in section 6 of [RFC3986] and to any normalization rules in its applicable URI scheme specification.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "A DID Document MUST be a single JSON object conforming to [RFC8259].", + "optional": false, + "title": "A DID Document MUST be a single JSON object conforming to [RFC8259].", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + } + ] +} diff --git a/implementations/report-template.hbs b/implementations/report-template.hbs new file mode 100644 index 0000000..b683f9b --- /dev/null +++ b/implementations/report-template.hbs @@ -0,0 +1,10 @@ + + + + {{> head.hbs}} + + + {{> implementation.hbs}} + {{> report.hbs}} + + diff --git a/implementations/report-template.js b/implementations/report-template.js new file mode 100644 index 0000000..6bc64d3 --- /dev/null +++ b/implementations/report-template.js @@ -0,0 +1,13 @@ +(function() { + var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {}; +templates['report-template.hbs'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { + var stack1; + + return "\n\n \n" + + ((stack1 = container.invokePartial(partials["head.hbs"],depth0,{"name":"head.hbs","data":data,"indent":" ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "") + + " \n \n" + + ((stack1 = container.invokePartial(partials["implementation.hbs"],depth0,{"name":"implementation.hbs","data":data,"indent":" ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "") + + ((stack1 = container.invokePartial(partials["report.hbs"],depth0,{"name":"report.hbs","data":data,"indent":" ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "") + + " \n\n"; +},"usePartial":true,"useData":true}); +})(); \ No newline at end of file diff --git a/implementations/report.hbs b/implementations/report.hbs new file mode 100644 index 0000000..3e64fc9 --- /dev/null +++ b/implementations/report.hbs @@ -0,0 +1,37 @@ +
+

Conformance Testing Results

+

+ The results of the conformance testing are shown below: +

+ {{#each tests}} +
+

{{@key}}

+ + + + + {{#each @root.implementations}} + + {{/each}} + + + {{#each this}} + + + {{#each this}} + + {{/each}} + + {{/each}} + +
Test{{this}}
{{@key}}{{getStatusMark state}}
+
+ {{/each}} +
diff --git a/implementations/report.json b/implementations/report.json new file mode 100644 index 0000000..efb9f54 --- /dev/null +++ b/implementations/report.json @@ -0,0 +1,241 @@ +{ + "Identifier": [ + { + "fullTitle": "A DID method specification MUST further restrict the generic DID syntax by defining its own method-name and its own method-specific-id syntax.", + "optional": false, + "title": "A DID method specification MUST further restrict the generic DID syntax by defining its own method-name and its own method-specific-id syntax.", + "pending": false, + "state": "passed", + "duration": 2, + "speed": "fast", + "errors": "" + }, + { + "fullTitle": "A method-specific parameter name MUST be prefixed by the method name as defined by the method-name rule.", + "optional": false, + "title": "A method-specific parameter name MUST be prefixed by the method name as defined by the method-name rule.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "A generic DID path is identical to a URI path and MUST conform to the the path-abempty ABNF rule in [RFC3986].", + "optional": false, + "title": "A generic DID path is identical to a URI path and MUST conform to the the path-abempty ABNF rule in [RFC3986].", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "A generic DID query is identical to a URI query and MUST conform to the the query ABNF rule in [RFC3986].", + "optional": false, + "title": "A generic DID query is identical to a URI query and MUST conform to the the query ABNF rule in [RFC3986].", + "pending": false, + "state": "failed", + "duration": 1, + "errors": "Not Implemented" + }, + { + "fullTitle": "A generic DID fragment is identical to a URI fragment and MUST conform to the the fragment ABNF rule in [RFC3986]", + "optional": false, + "title": "A generic DID fragment is identical to a URI fragment and MUST conform to the the fragment ABNF rule in [RFC3986]", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "The did: scheme name MUST be lowercase.", + "optional": false, + "title": "The did: scheme name MUST be lowercase.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "(did) The method name MUST be lowercase.", + "optional": false, + "title": "(did) The method name MUST be lowercase.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + } + ], + "Document": [ + { + "fullTitle": "The value of the @context property MUST be one or more URIs, where the value of the first URI ishttps://www.w3.org/2019/did/v1.", + "optional": false, + "title": "The value of the @context property MUST be one or more URIs, where the value of the first URI ishttps://www.w3.org/2019/did/v1.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "If more than one URI is provided, the URIs MUST be interpreted as an ordered set.", + "optional": false, + "title": "If more than one URI is provided, the URIs MUST be interpreted as an ordered set.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "DID Documents MUST include the @context property.", + "optional": false, + "title": "DID Documents MUST include the @context property.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "A DID Document MUST have exactly one top-level context statement.", + "optional": false, + "title": "A DID Document MUST have exactly one top-level context statement.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "The key for this property (context) MUST be @context.", + "optional": false, + "title": "The key for this property (context) MUST be @context.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "The value of this key MUST be at least the URL for the generic DID context: https://www.w3.org/2019/did/v1.", + "optional": false, + "title": "The value of this key MUST be at least the URL for the generic DID context: https://www.w3.org/2019/did/v1.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "Method-specific contexts MUST NOT override the terms defined in the generic DID context.", + "optional": false, + "title": "Method-specific contexts MUST NOT override the terms defined in the generic DID context.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "A DID Document MUST have exactly one DID subject.", + "optional": false, + "title": "A DID Document MUST have exactly one DID subject.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "The key for this property MUST be id.", + "optional": false, + "title": "The key for this property MUST be id.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "The value of this key MUST be a valid DID.", + "optional": false, + "title": "The value of this key MUST be a valid DID.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "However, the fully resolved DID Document MUST contain a valid id property.", + "optional": false, + "title": "However, the fully resolved DID Document MUST contain a valid id property.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "A DID Document that contains a revoked key MUST also contain or refer to the revocation information for the key (e.g., a revocation list).", + "optional": false, + "title": "A DID Document that contains a revoked key MUST also contain or refer to the revocation information for the key (e.g., a revocation list).", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "The value of the publicKey property MUST be an array of public keys.", + "optional": false, + "title": "The value of the publicKey property MUST be an array of public keys.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "Each public key MUST include id and type properties, and exactly one value property. The array of public keys MUST NOT contain duplicate entries with the same id.", + "optional": false, + "title": "Each public key MUST include id and type properties, and exactly one value property. The array of public keys MUST NOT contain duplicate entries with the same id.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "Each public key MUST include a controller property, which identifies the controller of the corresponding private key.", + "optional": false, + "title": "Each public key MUST include a controller property, which identifies the controller of the corresponding private key.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "The value property of a public key MUST be exactly one of publicKeyPem, publicKeyJwk, publicKeyHex, publicKeyBase64, publicKeyBase58, publicKeyMultibase, depending on the format and encoding of the public key.", + "optional": false, + "title": "The value property of a public key MUST be exactly one of publicKeyPem, publicKeyJwk, publicKeyHex, publicKeyBase64, publicKeyBase58, publicKeyMultibase, depending on the format and encoding of the public key.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "Each service endpoint MUSTmust include id, type, and serviceEndpoint properties, and MAY include additional properties.", + "optional": false, + "title": "Each service endpoint MUSTmust include id, type, and serviceEndpoint properties, and MAY include additional properties.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "The value of the serviceEndpoint property MUST be a JSON-LD object or a valid URI conforming to [RFC3986] and normalized according to the rules in section 6 of [RFC3986] and to any normalization rules in its applicable URI scheme specification.", + "optional": false, + "title": "The value of the serviceEndpoint property MUST be a JSON-LD object or a valid URI conforming to [RFC3986] and normalized according to the rules in section 6 of [RFC3986] and to any normalization rules in its applicable URI scheme specification.", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + }, + { + "fullTitle": "A DID Document MUST be a single JSON object conforming to [RFC8259].", + "optional": false, + "title": "A DID Document MUST be a single JSON object conforming to [RFC8259].", + "pending": false, + "state": "failed", + "duration": 0, + "errors": "Not Implemented" + } + ] +} diff --git a/implementations/test.json b/implementations/test.json index a4f697c..b9337c7 100644 --- a/implementations/test.json +++ b/implementations/test.json @@ -1,3 +1,3 @@ { - "implementation": "all_tests" + "implementation": "empty" } diff --git a/package.json b/package.json index b73457c..d318d20 100644 --- a/package.json +++ b/package.json @@ -8,12 +8,13 @@ "chai-as-promised": "^7.1.1", "eslint": "^5.16.0", "eslint-config-digitalbazaar": "^2.0.0", - "mocha": "^5.2.0" + "handlebars": "^4.1.2", + "mocha": "^6.1.4" }, "scripts": { - "report": "mocha --recursive test/latest/ -R json > implementations/report.json", + "report": "mocha --require ./test/mocha-setup.js --recursive test/latest/ -R ImplementationReporter > implementations/report.json", "summary": "cd implementations && node generate.js", - "test": "mocha --require ./test/chai-extend.js --recursive test/latest/" + "test": "mocha --require ./test/mocha-setup.js --recursive test/latest/" }, "repository": { "type": "git", diff --git a/test/ImplementationReporter.js b/test/ImplementationReporter.js new file mode 100644 index 0000000..6d3488a --- /dev/null +++ b/test/ImplementationReporter.js @@ -0,0 +1,74 @@ +const mocha = require('mocha'); + +exports = module.exports = ImplementationReporter; + +/** + * Custom Mocha Reporter for w3c test suites. + * + * @param {Function} runner - A mocha runner. + * @param {Object} options - The command line options passed to mocha. + */ +function ImplementationReporter(runner, options) { + mocha.reporters.Base.call(this, runner, options); + const report = {}; + // add new fields for the final report here. + function formatTest(test, parentSuite = '') { + // remove line breaks and the parentSuite name + const fullTitle = test + .fullTitle() + .replace(/\s\s/g, '') + .replace(parentSuite, '') + .trim(); + return { + fullTitle, + // just in case the parentSuite contains optional + optional: /optional/i.test(test.fullTitle()), + title: test.title.replace(/\s\s/g, ''), + pending: test.pending, + state: test.state, + duration: test.duration, + speed: test.speed, + errors: test.err ? test.err.message : '' + }; + } + // recurse through suites collecting all their finished tests. + function addSubTests(suites, title) { + suites.forEach(s => { + report[title] = report[title].concat(s.tests); + if(s.suites.length) { + addSubTests(s.suites, title); + } + }); + } + runner.on(mocha.Runner.constants.EVENT_SUITE_BEGIN, function(suite) { + // the parent suite will setup the report structure. + if(!suite.parent) { + suite.suites.forEach(s => { + report[s.title] = []; + }); + } + }); + runner.on(mocha.Runner.constants.EVENT_SUITE_END, function(suite) { + // we only want the top level commands. + const topSuites = Object.keys(report); + if(topSuites.includes(suite.title)) { + report[suite.title] = report[suite.title].concat(suite.tests); + addSubTests(suite.suites, suite.title); + } + }); + runner.on(mocha.Runner.constants.EVENT_RUN_END, function() { + try { + Object.keys(report).forEach(name => { + // create a function for each test under this name + const formatter = test => formatTest(test, name); + // move all optional tests to the bottom + report[name] = report[name] + .map(formatter) + .sort((a, b) => a.optional - b.optional); + }); + console.log(JSON.stringify(report, null, 2)); + } catch(e) { + console.error(e); + } + }); +} diff --git a/test/assertions.js b/test/assertions.js index e8b2b96..06a900d 100644 --- a/test/assertions.js +++ b/test/assertions.js @@ -43,3 +43,5 @@ exports.parseDID = parseDID; exports.uuid = 'did:uuid:0d2bae3e-4915-489c-bfa1-1ba14e8b43cd'; exports.methSpec = exports.uuid + ';uuid:extra=true'; exports.urn = 'did:urn:example:foo-bar-baz-qux?+CCResolve:cc=uk'; +// TODO https://www.chaijs.com/guide/plugins/ +// turn into a full on chai plugin diff --git a/test/mocha-setup.js b/test/mocha-setup.js new file mode 100644 index 0000000..52035a8 --- /dev/null +++ b/test/mocha-setup.js @@ -0,0 +1,8 @@ +const chai = require('chai'); +require('./chai-extend'); +const mocha = require('mocha'); +const ImplementationReporter = require('./ImplementationReporter'); +// configure chai +chai.should(); + +mocha.reporters.ImplementationReporter = ImplementationReporter;