diff --git a/tests/test_app.py b/tests/test_app.py index 18b8ef45..c3f5b2e8 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -1,8 +1,13 @@ from apispec.utils import validate_spec +import json +import jsonschema +import os + from openflexure_microscope.api.app import api_microscope, app, labthing from openflexure_microscope.camera.base import BaseCamera from openflexure_microscope.microscope import Microscope from openflexure_microscope.stage.base import BaseStage +from labthings.json import encode_json def test_app_creation(): @@ -17,3 +22,24 @@ def test_microscope_creation(): def test_openapi_valid(): assert validate_spec(labthing.spec) + +def test_thing_description_valid(): + # TODO: it would be nice to put this into LabThings + # First load the schema from file and check it validates + schema_fname = os.path.join( + os.path.dirname(__file__), + "w3c_td_schema.json" + ) + schema = json.load(open(schema_fname, "r")) + jsonschema.Draft7Validator.check_schema(schema) + + # Build a TD dictionary + with labthing.app.test_request_context(): + td_dict = labthing.thing_description.to_dict() + + # Allow our LabThingsJSONEncoder to encode the RD + td_json = encode_json(td_dict) + # Decode the JSON back into a primitive dictionary + td_json_dict = json.loads(td_json) + # Validate + jsonschema.validate(instance=td_json_dict, schema=schema) \ No newline at end of file diff --git a/tests/w3c_td_schema.json b/tests/w3c_td_schema.json new file mode 100644 index 00000000..62194083 --- /dev/null +++ b/tests/w3c_td_schema.json @@ -0,0 +1,1035 @@ +{ + "title": "WoT TD Schema - 16 October 2019", + "description": "JSON Schema for validating TD instances against the TD model. TD instances can be with or without terms that have default values", + "$schema ": "http://json-schema.org/draft-07/schema#", + "definitions": { + "anyUri": { + "type": "string", + "format": "iri-reference" + }, + "description": { + "type": "string" + }, + "descriptions": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "title": { + "type": "string" + }, + "titles": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "security": { + "oneOf": [{ + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "scopes": { + "oneOf": [{ + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "subprotocol": { + "type": "string", + "enum": [ + "longpoll", + "websub", + "sse" + ] + }, + "thing-context-w3c-uri": { + "type": "string", + "enum": [ + "https://www.w3.org/2019/wot/td/v1" + ] + }, + "thing-context": { + "oneOf": [{ + "type": "array", + "items": [{ + "$ref": "#/definitions/thing-context-w3c-uri" + }], + "additionalItems": { + "anyOf": [{ + "$ref": "#/definitions/anyUri" + }, + { + "type": "object" + } + ] + } + }, + { + "$ref": "#/definitions/thing-context-w3c-uri" + } + ] + }, + "type_declaration": { + "oneOf": [{ + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "dataSchema": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "title": { + "$ref": "#/definitions/title" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "titles": { + "$ref": "#/definitions/titles" + }, + "writeOnly": { + "type": "boolean" + }, + "readOnly": { + "type": "boolean" + }, + "oneOf": { + "type": "array", + "items": { + "$ref": "#/definitions/dataSchema" + } + }, + "unit": { + "type": "string" + }, + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true + }, + "format": { + "type": "string" + }, + "const": {}, + "type": { + "type": "string", + "enum": [ + "boolean", + "integer", + "number", + "string", + "object", + "array", + "null" + ] + }, + "items": { + "oneOf": [{ + "$ref": "#/definitions/dataSchema" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/dataSchema" + } + } + ] + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "minItems": { + "type": "integer", + "minimum": 0 + }, + "minimum": { + "type": "number" + }, + "maximum": { + "type": "number" + }, + "properties": { + "additionalProperties": { + "$ref": "#/definitions/dataSchema" + } + }, + "required": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "form_element_property": { + "type": "object", + "properties": { + "op": { + "oneOf": [{ + "type": "string", + "enum": [ + "readproperty", + "writeproperty", + "observeproperty", + "unobserveproperty" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "readproperty", + "writeproperty", + "observeproperty", + "unobserveproperty" + ] + } + } + ] + }, + "href": { + "$ref": "#/definitions/anyUri" + }, + "contentType": { + "type": "string" + }, + "contentCoding": { + "type": "string" + }, + "subprotocol": { + "$ref": "#/definitions/subprotocol" + }, + "security": { + "$ref": "#/definitions/security" + }, + "scopes": { + "$ref": "#/definitions/scopes" + }, + "response": { + "type": "object", + "properties": { + "contentType": { + "type": "string" + } + } + } + }, + "required": [ + "href" + ], + "additionalProperties": true + }, + "form_element_action": { + "type": "object", + "properties": { + "op": { + "oneOf": [{ + "type": "string", + "enum": [ + "invokeaction" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "invokeaction" + ] + } + } + ] + }, + "href": { + "$ref": "#/definitions/anyUri" + }, + "contentType": { + "type": "string" + }, + "contentCoding": { + "type": "string" + }, + "subprotocol": { + "$ref": "#/definitions/subprotocol" + }, + "security": { + "$ref": "#/definitions/security" + }, + "scopes": { + "$ref": "#/definitions/scopes" + }, + "response": { + "type": "object", + "properties": { + "contentType": { + "type": "string" + } + } + } + }, + "required": [ + "href" + ], + "additionalProperties": true + }, + "form_element_event": { + "type": "object", + "properties": { + "op": { + "oneOf": [{ + "type": "string", + "enum": [ + "subscribeevent", + "unsubscribeevent" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "subscribeevent", + "unsubscribeevent" + ] + } + } + ] + }, + "href": { + "$ref": "#/definitions/anyUri" + }, + "contentType": { + "type": "string" + }, + "contentCoding": { + "type": "string" + }, + "subprotocol": { + "$ref": "#/definitions/subprotocol" + }, + "security": { + "$ref": "#/definitions/security" + }, + "scopes": { + "$ref": "#/definitions/scopes" + }, + "response": { + "type": "object", + "properties": { + "contentType": { + "type": "string" + } + } + } + }, + "required": [ + "href" + ], + "additionalProperties": true + }, + "form_element_root": { + "type": "object", + "properties": { + "op": { + "oneOf": [{ + "type": "string", + "enum": [ + "readallproperties", + "writeallproperties", + "readmultipleproperties", + "writemultipleproperties" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "readallproperties", + "writeallproperties", + "readmultipleproperties", + "writemultipleproperties" + ] + } + } + ] + }, + "href": { + "$ref": "#/definitions/anyUri" + }, + "contentType": { + "type": "string" + }, + "contentCoding": { + "type": "string" + }, + "subprotocol": { + "$ref": "#/definitions/subprotocol" + }, + "security": { + "$ref": "#/definitions/security" + }, + "scopes": { + "$ref": "#/definitions/scopes" + }, + "response": { + "type": "object", + "properties": { + "contentType": { + "type": "string" + } + } + } + }, + "required": [ + "href" + ], + "additionalProperties": true + }, + "property_element": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "title": { + "$ref": "#/definitions/title" + }, + "titles": { + "$ref": "#/definitions/titles" + }, + "forms": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/form_element_property" + } + }, + "uriVariables": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/dataSchema" + } + }, + "observable": { + "type": "boolean" + }, + "writeOnly": { + "type": "boolean" + }, + "readOnly": { + "type": "boolean" + }, + "oneOf": { + "type": "array", + "items": { + "$ref": "#/definitions/dataSchema" + } + }, + "unit": { + "type": "string" + }, + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true + }, + "format": { + "type": "string" + }, + "const": {}, + "type": { + "type": "string", + "enum": [ + "boolean", + "integer", + "number", + "string", + "object", + "array", + "null" + ] + }, + "items": { + "oneOf": [{ + "$ref": "#/definitions/dataSchema" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/dataSchema" + } + } + ] + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "minItems": { + "type": "integer", + "minimum": 0 + }, + "minimum": { + "type": "number" + }, + "maximum": { + "type": "number" + }, + "properties": { + "additionalProperties": { + "$ref": "#/definitions/dataSchema" + } + }, + "required": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "forms" + ], + "additionalProperties": true + }, + "action_element": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "title": { + "$ref": "#/definitions/title" + }, + "titles": { + "$ref": "#/definitions/titles" + }, + "forms": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/form_element_action" + } + }, + "uriVariables": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/dataSchema" + } + }, + "input": { + "$ref": "#/definitions/dataSchema" + }, + "output": { + "$ref": "#/definitions/dataSchema" + }, + "safe": { + "type": "boolean" + }, + "idempotent": { + "type": "boolean" + } + }, + "required": [ + "forms" + ], + "additionalProperties": true + }, + "event_element": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "title": { + "$ref": "#/definitions/title" + }, + "titles": { + "$ref": "#/definitions/titles" + }, + "forms": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/form_element_event" + } + }, + "uriVariables": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/dataSchema" + } + }, + "subscription": { + "$ref": "#/definitions/dataSchema" + }, + "data": { + "$ref": "#/definitions/dataSchema" + }, + "cancellation": { + "$ref": "#/definitions/dataSchema" + } + }, + "required": [ + "forms" + ], + "additionalProperties": true + }, + "link_element": { + "type": "object", + "properties": { + "href": { + "$ref": "#/definitions/anyUri" + }, + "type": { + "type": "string" + }, + "rel": { + "type": "string" + }, + "anchor": { + "$ref": "#/definitions/anyUri" + } + }, + "required": [ + "href" + ], + "additionalProperties": true + }, + "securityScheme": { + "oneOf": [{ + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": [ + "nosec" + ] + } + }, + "required": [ + "scheme" + ] + }, + { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": [ + "basic" + ] + }, + "in": { + "type": "string", + "enum": [ + "header", + "query", + "body", + "cookie" + ] + }, + "name": { + "type": "string" + } + }, + "required": [ + "scheme" + ] + }, + { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": [ + "digest" + ] + }, + "qop": { + "type": "string", + "enum": [ + "auth", + "auth-int" + ] + }, + "in": { + "type": "string", + "enum": [ + "header", + "query", + "body", + "cookie" + ] + }, + "name": { + "type": "string" + } + }, + "required": [ + "scheme" + ] + }, + { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": [ + "apikey" + ] + }, + "in": { + "type": "string", + "enum": [ + "header", + "query", + "body", + "cookie" + ] + }, + "name": { + "type": "string" + } + }, + "required": [ + "scheme" + ] + }, + { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": [ + "bearer" + ] + }, + "authorization": { + "$ref": "#/definitions/anyUri" + }, + "alg": { + "type": "string" + }, + "format": { + "type": "string" + }, + "in": { + "type": "string", + "enum": [ + "header", + "query", + "body", + "cookie" + ] + }, + "name": { + "type": "string" + } + }, + "required": [ + "scheme" + ] + }, + { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": [ + "psk" + ] + }, + "identity": { + "type": "string" + } + }, + "required": [ + "scheme" + ] + }, + { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": [ + "oauth2" + ] + }, + "authorization": { + "$ref": "#/definitions/anyUri" + }, + "token": { + "$ref": "#/definitions/anyUri" + }, + "refresh": { + "$ref": "#/definitions/anyUri" + }, + "scopes": { + "oneOf": [{ + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "flow": { + "type": "string", + "enum": [ + "code" + ] + } + }, + "required": [ + "scheme" + ] + } + ] + } + }, + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uri" + }, + "title": { + "$ref": "#/definitions/title" + }, + "titles": { + "$ref": "#/definitions/titles" + }, + "properties": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/property_element" + } + }, + "actions": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/action_element" + } + }, + "events": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/event_element" + } + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "version": { + "type": "object", + "properties": { + "instance": { + "type": "string" + } + }, + "required": [ + "instance" + ] + }, + "links": { + "type": "array", + "items": { + "$ref": "#/definitions/link_element" + } + }, + "forms": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/form_element_root" + } + }, + "base": { + "$ref": "#/definitions/anyUri" + }, + "securityDefinitions": { + "type": "object", + "minProperties": 1, + "additionalProperties": { + "$ref": "#/definitions/securityScheme" + } + }, + "support": { + "$ref": "#/definitions/anyUri" + }, + "created": { + "type": "string", + "format": "date-time" + }, + "modified": { + "type": "string", + "format": "date-time" + }, + "security": { + "oneOf": [{ + "type": "string" + }, + { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + ] + }, + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "@context": { + "$ref": "#/definitions/thing-context" + } + }, + "required": [ + "title", + "security", + "securityDefinitions", + "@context" + ], + "additionalProperties": true +} \ No newline at end of file