BREAKING CHANGE(lib): rewrite in TypeScript (#226)
This is a major rewrite of the entire codebase into TypeScript. Nearly all tests have been retained except where behavior is significantly different. Some highlights of these changes: * lowercase all CloudEvent properties and fix base64 encoded data Previously there was a format() function that would convert a CloudEvent object into JSON with all of the properties lowercased. With this rewrite a CloudEvent object can be converted to JSON simply with JSON.stringify(). However, in order to be compliant with the JSON representation outlined in the spec here https://github.com/cloudevents/spec/blob/v1.0/json-format.md all of the event properties must be all lowercase. * lib(transport): make transport mode an Enum * src: allow custom headers (#1) * lib(exports): export explicitly versioned names where appropriate * lib(cloudevent): modify ctor to accept extensions inline * lib(cloudevent): make extensions a part of the event object * test: convert all tests to typescript * examples: update all examples with latest API changes * docs: update README with latest API changes * src: add prettier for code style and fix a lot of linting errors * lib: move data decoding to occur within the CloudEvent object Signed-off-by: Lance Ball <lball@redhat.com>
This commit is contained in:
parent
060b21ba36
commit
276b810dd8
14
.eslintrc
14
.eslintrc
|
@ -1,5 +1,14 @@
|
|||
{
|
||||
"extends": "eslint:recommended",
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2015,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"extends": [
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"prettier/@typescript-eslint",
|
||||
"plugin:prettier/recommended"
|
||||
],
|
||||
"env": {
|
||||
"es6": true,
|
||||
"node": true,
|
||||
|
@ -7,7 +16,6 @@
|
|||
},
|
||||
"rules": {
|
||||
"no-var": "error",
|
||||
"space-before-function-paren": ["error", "never"],
|
||||
"standard/no-callback-literal": "off",
|
||||
"arrow-spacing": "error",
|
||||
"arrow-parens": ["error", "always"],
|
||||
|
@ -20,7 +28,7 @@
|
|||
"no-console": ["error", {
|
||||
"allow": ["warn", "error"]
|
||||
}],
|
||||
"valid-jsdoc": "error",
|
||||
"valid-jsdoc": "warn",
|
||||
"semi": ["error", "always"],
|
||||
"quotes": ["error", "double", { "allowTemplateLiterals": true }]
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ index.js
|
|||
/lib
|
||||
/browser
|
||||
/bundles
|
||||
/dist
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
module.exports = {
|
||||
semi: true,
|
||||
trailingComma: "all",
|
||||
doubleQuote: true,
|
||||
printWidth: 120,
|
||||
tabWidth: 2
|
||||
}
|
51
README.md
51
README.md
|
@ -36,33 +36,32 @@ binary and structured events in either the 1.0 or 0.3 protocol formats.
|
|||
```js
|
||||
const {
|
||||
CloudEvent,
|
||||
HTTPReceiver
|
||||
Receiver
|
||||
} = require("cloudevents-sdk");
|
||||
|
||||
// Create a receiver to accept events over HTTP
|
||||
const receiver = new HTTPReceiver();
|
||||
const receiver = new Receiver();
|
||||
|
||||
// body and headers come from an incoming HTTP request, e.g. express.js
|
||||
const receivedEvent = receiver.accept(req.headers, req.body);
|
||||
console.log(receivedEvent.format());
|
||||
console.log(receivedEvent);
|
||||
```
|
||||
|
||||
#### Emitting Events
|
||||
|
||||
To emit events, you'll need to decide whether the event should be sent in
|
||||
binary or structured format, and determine what version of the CloudEvents
|
||||
specification you want to send the event as.
|
||||
You can send events over HTTP in either binary or structured format.
|
||||
|
||||
By default, the `HTTPEmitter` will emit events over HTTP POST using the
|
||||
latest supported specification version, in binary mode. You can emit version specific events by providing
|
||||
the specication version in the constructor to `HTTPEmitter`. To send
|
||||
structured events, add that string as a parameter to `emitter.send()`.
|
||||
By default, the `Emitter` will emit events over HTTP POST using the
|
||||
binary transport protocol. The `Emitter` will examine the `specversion`
|
||||
of the event being sent, and use the appropriate protocol version. To send
|
||||
structured events, add `Protocol.HTTPStructured` as a parameter to
|
||||
`emitter.send()`.
|
||||
|
||||
```js
|
||||
const { CloudEvent, HTTPEmitter } = require("cloudevents-sdk");
|
||||
const { CloudEvent, Emitter, Protocol, Version } = require("cloudevents-sdk");
|
||||
|
||||
// With only an endpoint URL, this creates a v1 emitter
|
||||
const v1Emitter = new HTTPEmitter({
|
||||
const emitter = new Emitter({
|
||||
url: "https://cloudevents.io/example"
|
||||
});
|
||||
const event = new CloudEvent({
|
||||
|
@ -70,39 +69,37 @@ const event = new CloudEvent({
|
|||
});
|
||||
|
||||
// By default, the emitter will send binary events
|
||||
v1Emitter.send(event).then((response) => {
|
||||
emitter.send(event).then((response) => {
|
||||
// handle the response
|
||||
}).catch(console.error);
|
||||
|
||||
// To send a structured event, just add that as an option
|
||||
v1Emitter.send(event, { mode: "structured" })
|
||||
emitter.send(event, { protocol: Protocol.HTTPStructured })
|
||||
.then((response) => {
|
||||
// handle the response
|
||||
}).catch(console.error);
|
||||
|
||||
// To send an event to an alternate URL, add that as an option
|
||||
v1Emitter.send(event, { url: "https://alternate.com/api" })
|
||||
emitter.send(event, { url: "https://alternate.com/api" })
|
||||
.then((response) => {
|
||||
// handle the response
|
||||
}).catch(console.error);
|
||||
|
||||
// Sending a v0.3 event works the same, just let the emitter know when
|
||||
// you create it that you are working with the 0.3 spec
|
||||
const v03Emitter = new HTTPEmitter({
|
||||
url: "https://cloudevents.io/example",
|
||||
version: "0.3"
|
||||
});
|
||||
|
||||
// Again, the default is to send binary events
|
||||
// To send a structured event or to an alternate URL, provide those
|
||||
// as parameters in a options object as above
|
||||
v3Emitter.send(event)
|
||||
// Sending a v0.3 event works the same, If your event has a
|
||||
// specversion property of Version.V03, then it will be sent
|
||||
// using the 0.3 transport protocol
|
||||
emitter.send(new CloudEvent({ specversion: Version.V03, source, type }))
|
||||
.then((response) => {
|
||||
// handle the response
|
||||
}).catch(console.error);
|
||||
|
||||
```
|
||||
|
||||
### Example Applications
|
||||
|
||||
There are a few trivial example applications in
|
||||
[the examples folder](https://github.com/cloudevents/sdk-javascript/tree/master/examples).
|
||||
There you will find Express.js, TypeScript and Websocket examples.
|
||||
|
||||
## Supported specification features
|
||||
|
||||
| | [v0.3](https://github.com/cloudevents/spec/tree/v0.3) | [v1.0](https://github.com/cloudevents/spec/tree/v1.0) |
|
||||
|
|
|
@ -1,42 +1,39 @@
|
|||
/* eslint-disable no-console */
|
||||
|
||||
const express = require("express");
|
||||
const { HTTPReceiver } = require("cloudevents-sdk");
|
||||
const { Receiver } = require("cloudevents-sdk");
|
||||
|
||||
const app = express();
|
||||
const receiver = new HTTPReceiver();
|
||||
const receiver = new Receiver();
|
||||
|
||||
app.use((req, res, next) => {
|
||||
let data = "";
|
||||
|
||||
req.setEncoding("utf8");
|
||||
req.on("data", function(chunk) {
|
||||
req.on("data", function (chunk) {
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
req.on("end", function() {
|
||||
req.on("end", function () {
|
||||
req.body = data;
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
app.post("/", function(req, res) {
|
||||
console.log(req.headers);
|
||||
console.log(req.body);
|
||||
app.post("/", function (req, res) {
|
||||
console.log("HEADERS", req.headers);
|
||||
console.log("BODY", req.body);
|
||||
|
||||
try {
|
||||
const event = receiver.accept(req.headers, req.body);
|
||||
const asJSON = event.format();
|
||||
console.log(`Accepted event: ${JSON.stringify(event.format(), null, 2)}`);
|
||||
res.status(201).json(asJSON);
|
||||
console.log(`Accepted event: ${event}`);
|
||||
res.status(201).json(event);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(415)
|
||||
.header("Content-Type", "application/json")
|
||||
.send(JSON.stringify(err));
|
||||
res.status(415).header("Content-Type", "application/json").send(JSON.stringify(err));
|
||||
}
|
||||
});
|
||||
|
||||
app.listen(3000, function() {
|
||||
app.listen(3000, function () {
|
||||
console.log("Example app listening on port 3000!");
|
||||
});
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
"check": "gts check",
|
||||
"clean": "gts clean",
|
||||
"compile": "tsc -p .",
|
||||
"watch": "tsc -p . --watch",
|
||||
"fix": "gts fix",
|
||||
"prepare": "npm run compile",
|
||||
"pretest": "npm run compile",
|
||||
|
|
|
@ -1,35 +1,33 @@
|
|||
import { CloudEvent, HTTPReceiver } from "cloudevents-sdk";
|
||||
import { CloudEventV1 } from "cloudevents-sdk/lib/v1";
|
||||
import { CloudEvent, CloudEventV1, Receiver } from "cloudevents-sdk";
|
||||
|
||||
export function doSomeStuff() {
|
||||
const receiver = new HTTPReceiver();
|
||||
const receiver = new Receiver();
|
||||
|
||||
const myevent: CloudEventV1 = new CloudEvent({
|
||||
source: "/source",
|
||||
type: "type",
|
||||
dataContentType: "text/plain",
|
||||
dataSchema: "https://d.schema.com/my.json",
|
||||
datacontenttype: "text/plain",
|
||||
dataschema: "https://d.schema.com/my.json",
|
||||
subject: "cha.json",
|
||||
data: "my-data"
|
||||
data: "my-data",
|
||||
});
|
||||
myevent.addExtension("extension-1", "some extension data");
|
||||
myevent.extension1 = "some extension data";
|
||||
|
||||
console.log("My structured event:", myevent.toString());
|
||||
console.log("My structured event extensions:", myevent.getExtensions());
|
||||
console.log("My structured event:", myevent);
|
||||
|
||||
// ------ receiver structured
|
||||
// The header names should be standarized to use lowercase
|
||||
const headers = {
|
||||
"content-type": "application/cloudevents+json"
|
||||
"content-type": "application/cloudevents+json",
|
||||
};
|
||||
|
||||
// Typically used with an incoming HTTP request where myevent.format() is the actual
|
||||
// body of the HTTP
|
||||
console.log("Received structured event:", receiver.accept(headers, myevent.format()).toString());
|
||||
console.log("Received structured event:", receiver.accept(headers, myevent));
|
||||
|
||||
// ------ receiver binary
|
||||
const data = {
|
||||
"data": "dataString"
|
||||
data: "dataString",
|
||||
};
|
||||
const attributes = {
|
||||
"ce-type": "type",
|
||||
|
@ -39,11 +37,11 @@ export function doSomeStuff() {
|
|||
"ce-time": "2019-06-16T11:42:00Z",
|
||||
"ce-dataschema": "http://schema.registry/v1",
|
||||
"Content-Type": "application/json",
|
||||
"ce-extension1": "extension1"
|
||||
"ce-extension1": "extension1",
|
||||
};
|
||||
|
||||
console.log("My binary event:", receiver.accept(attributes, data).toString());
|
||||
console.log("My binary event extensions:", receiver.accept(attributes, data).toString());
|
||||
console.log("My binary event:", receiver.accept(attributes, data));
|
||||
console.log("My binary event extensions:", receiver.accept(attributes, data));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ const { CloudEvent } = require("cloudevents-sdk");
|
|||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
output: process.stdout,
|
||||
});
|
||||
|
||||
rl.on("close", (_) => console.log("\n\nConnection closed! Press CTL-C to exit."));
|
||||
|
@ -25,16 +25,16 @@ ws.on("message", function incoming(message) {
|
|||
function ask() {
|
||||
rl.question("Would you like to see the current weather? Provide a zip code: ", function (zip) {
|
||||
console.log("Fetching weather data from server...");
|
||||
ws.send(new CloudEvent({
|
||||
const event = new CloudEvent({
|
||||
type: "weather.query",
|
||||
source: "/weather.client",
|
||||
data: zip
|
||||
}).toString());
|
||||
data: { zip },
|
||||
});
|
||||
ws.send(event.toString());
|
||||
});
|
||||
}
|
||||
|
||||
function print(data) {
|
||||
data = JSON.parse(data);
|
||||
console.log(`
|
||||
Current weather for ${data.name}: ${data.weather[0].main}
|
||||
------------------------------------------
|
||||
|
|
|
@ -2,13 +2,14 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>CloudEvent Example</title>
|
||||
<script src="../../bundles/cloudevents-sdk.js"></script>
|
||||
<script src="../../_bundles/cloudevents-sdk.js"></script>
|
||||
<script>
|
||||
const CloudEvent = window['cloudevents-sdk'].CloudEvent;
|
||||
const Version = window['cloudevents-sdk'].Version;
|
||||
const socket = new WebSocket("ws://localhost:8080");
|
||||
|
||||
function print(weather) {
|
||||
const data = JSON.parse(weather);
|
||||
const data = weather;
|
||||
const summary = `
|
||||
<h2>Current weather for ${data.name}: ${data.weather[0].main}</h2>
|
||||
<hr/>
|
||||
|
@ -23,7 +24,7 @@ and the wind is blowing at ${Math.round(data.wind.speed)}mph.
|
|||
|
||||
function initialize() {
|
||||
socket.onmessage = function(message) {
|
||||
console.log(message.data)
|
||||
console.log(message.data);
|
||||
const event = new CloudEvent(JSON.parse(message.data));
|
||||
if (event.type === "weather.error") {
|
||||
console.error(`Error: ${event.data}`);
|
||||
|
@ -37,11 +38,13 @@ and the wind is blowing at ${Math.round(data.wind.speed)}mph.
|
|||
input.addEventListener("keyup", function(event) {
|
||||
if (event.keyCode === 13) {
|
||||
event.preventDefault();
|
||||
socket.send(new CloudEvent({
|
||||
const ce = new CloudEvent({
|
||||
type: "weather.query",
|
||||
source: "/weather.client",
|
||||
data: input.value
|
||||
}).toString());
|
||||
data: { zip: input.value }
|
||||
});
|
||||
console.log(ce);
|
||||
socket.send(JSON.stringify(ce));
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -13,24 +13,29 @@ console.log("WebSocket server started. Waiting for events.");
|
|||
wss.on("connection", function connection(ws) {
|
||||
console.log("Connection received");
|
||||
ws.on("message", function incoming(message) {
|
||||
console.log(`Message received: ${message}`);
|
||||
const event = new CloudEvent(JSON.parse(message));
|
||||
console.log(`Message received: ${event.toString()}`);
|
||||
fetch(event.data)
|
||||
fetch(event.data.zip)
|
||||
.then((weather) => {
|
||||
ws.send(new CloudEvent({
|
||||
const response = new CloudEvent({
|
||||
dataContentType: "application/json",
|
||||
type: "current.weather",
|
||||
source: "/weather.server",
|
||||
data: weather
|
||||
}).toString());
|
||||
data: weather,
|
||||
});
|
||||
ws.send(JSON.stringify(response));
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
ws.send(new CloudEvent({
|
||||
type: "weather.error",
|
||||
source: "/weather.server",
|
||||
data: err.toString()
|
||||
}).toString());
|
||||
ws.send(
|
||||
JSON.stringify(
|
||||
new CloudEvent({
|
||||
type: "weather.error",
|
||||
source: "/weather.server",
|
||||
data: err.toString(),
|
||||
}),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -39,7 +44,7 @@ function fetch(zip) {
|
|||
const query = `${api}?zip=${zip}&appid=${key}&units=imperial`;
|
||||
return new Promise((resolve, reject) => {
|
||||
got(query)
|
||||
.then((response) => resolve(response.body))
|
||||
.then((response) => resolve(JSON.parse(response.body)))
|
||||
.catch((err) => reject(err.message));
|
||||
});
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
29
package.json
29
package.json
|
@ -2,26 +2,23 @@
|
|||
"name": "cloudevents-sdk",
|
||||
"version": "2.0.2",
|
||||
"description": "CloudEvents SDK for JavaScript",
|
||||
"main": "index.js",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"watch": "tsc --project tsconfig.json --watch",
|
||||
"build": "tsc --project tsconfig.json && tsc --project tsconfig.browser.json && webpack",
|
||||
"prelint": "npm run build",
|
||||
"lint": "standardx src examples",
|
||||
"fix": "standardx --fix",
|
||||
"pretest": "npm run lint && npm run test:ts",
|
||||
"test": "mocha test/**/*.js",
|
||||
"test:ts": "mocha --require ts-node/register ./test-ts/**/*.ts",
|
||||
"lint": "eslint 'src/**/*.{js,ts}' 'test/**/*.{js,ts}'",
|
||||
"lint:fix": "eslint 'src/**/*.{js,ts}' 'test/**/*.{js,ts}' --fix",
|
||||
"pretest": "npm run lint",
|
||||
"test": "mocha --require ts-node/register ./test/**/*.ts",
|
||||
"coverage": "nyc --reporter=lcov --reporter=text npm run test",
|
||||
"coverage-publish": "wget -qO - https://coverage.codacy.com/get.sh | bash -s report -l JavaScript -r coverage/lcov.info",
|
||||
"generate-docs": "typedoc --excludeNotDocumented --out docs src",
|
||||
"release": "standard-version",
|
||||
"prepublish": "npm run build"
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"files": [
|
||||
"index.js",
|
||||
"index.d.ts",
|
||||
"lib"
|
||||
"dist",
|
||||
"bundles"
|
||||
],
|
||||
"standard-version": {
|
||||
"types": [
|
||||
|
@ -108,15 +105,21 @@
|
|||
"@types/chai": "^4.2.11",
|
||||
"@types/mocha": "^7.0.2",
|
||||
"@types/node": "^13.13.9",
|
||||
"@types/uuid": "^8.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^3.4.0",
|
||||
"@typescript-eslint/parser": "^3.4.0",
|
||||
"chai": "~4.2.0",
|
||||
"eslint": "^7.3.0",
|
||||
"eslint-config-prettier": "^6.11.0",
|
||||
"eslint-config-standard": "^14.1.1",
|
||||
"eslint-plugin-import": "^2.20.2",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-prettier": "^3.1.4",
|
||||
"mocha": "~7.1.1",
|
||||
"nock": "~12.0.3",
|
||||
"nyc": "~15.0.0",
|
||||
"prettier": "^2.0.5",
|
||||
"standard-version": "^7.1.0",
|
||||
"standardx": "^5.0.0",
|
||||
"ts-node": "^8.10.2",
|
||||
"typedoc": "^0.17.7",
|
||||
"typescript": "^3.8.3",
|
||||
|
@ -126,5 +129,5 @@
|
|||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"types": "index.d.ts"
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
|
|
|
@ -1,74 +1,53 @@
|
|||
// Commons
|
||||
module.exports = Object.freeze({
|
||||
HEADERS: "headers",
|
||||
const CONSTANTS = Object.freeze({
|
||||
CHARSET_DEFAULT: "utf-8",
|
||||
|
||||
BINARY: "binary",
|
||||
STRUCTURED: "structured",
|
||||
|
||||
SPEC_V03: "0.3",
|
||||
SPEC_V1: "1.0",
|
||||
|
||||
DEFAULT_SPEC_VERSION_HEADER: "ce-specversion",
|
||||
|
||||
EXTENSIONS_PREFIX: "ce-",
|
||||
ENCODING_BASE64: "base64",
|
||||
|
||||
DATA_ATTRIBUTE: "data",
|
||||
|
||||
MIME_JSON: "application/json",
|
||||
MIME_OCTET_STREAM: "application/octet-stream",
|
||||
MIME_CE: "application/cloudevents",
|
||||
MIME_CE_JSON: "application/cloudevents+json",
|
||||
|
||||
HEADER_CONTENT_TYPE: "content-type",
|
||||
|
||||
DEFAULT_CONTENT_TYPE: "application/json; charset=utf-8",
|
||||
DEFAULT_CE_CONTENT_TYPE: "application/cloudevents+json; charset=utf-8",
|
||||
|
||||
BINARY_HEADERS_03: {
|
||||
CE_HEADERS: {
|
||||
TYPE: "ce-type",
|
||||
SPEC_VERSION: "ce-specversion",
|
||||
SOURCE: "ce-source",
|
||||
ID: "ce-id",
|
||||
TIME: "ce-time",
|
||||
SCHEMA_URL: "ce-schemaurl",
|
||||
CONTENT_ENCODING: "ce-datacontentencoding",
|
||||
SUBJECT: "ce-subject",
|
||||
EXTENSIONS_PREFIX: "ce-"
|
||||
},
|
||||
STRUCTURED_ATTRS_03: {
|
||||
TYPE: "type",
|
||||
SPEC_VERSION: "specversion",
|
||||
SOURCE: "source",
|
||||
ID: "id",
|
||||
TIME: "time",
|
||||
SCHEMA_URL: "schemaurl",
|
||||
CONTENT_ENCODING: "datacontentencoding",
|
||||
CONTENT_TYPE: "datacontenttype",
|
||||
SUBJECT: "subject",
|
||||
DATA: "data"
|
||||
},
|
||||
|
||||
BINARY_HEADERS_1: {
|
||||
TYPE: "ce-type",
|
||||
SPEC_VERSION: "ce-specversion",
|
||||
SOURCE: "ce-source",
|
||||
ID: "ce-id",
|
||||
TIME: "ce-time",
|
||||
DATA_SCHEMA: "ce-dataschema",
|
||||
SUBJECT: "ce-subject",
|
||||
EXTENSIONS_PREFIX: "ce-"
|
||||
},
|
||||
STRUCTURED_ATTRS_1: {
|
||||
TYPE: "type",
|
||||
SPEC_VERSION: "specversion",
|
||||
SOURCE: "source",
|
||||
CE_ATTRIBUTES: {
|
||||
ID: "id",
|
||||
TYPE: "type",
|
||||
SOURCE: "source",
|
||||
SPEC_VERSION: "specversion",
|
||||
TIME: "time",
|
||||
DATA_SCHEMA: "dataschema",
|
||||
CONTENT_TYPE: "datacontenttype",
|
||||
SUBJECT: "subject",
|
||||
DATA: "data",
|
||||
DATA_BASE64: "data_base64"
|
||||
}
|
||||
},
|
||||
|
||||
BINARY_HEADERS_03: {
|
||||
SCHEMA_URL: "ce-schemaurl",
|
||||
CONTENT_ENCODING: "ce-datacontentencoding",
|
||||
},
|
||||
STRUCTURED_ATTRS_03: {
|
||||
SCHEMA_URL: "schemaurl",
|
||||
CONTENT_ENCODING: "datacontentencoding",
|
||||
},
|
||||
|
||||
BINARY_HEADERS_1: {
|
||||
DATA_SCHEMA: "ce-dataschema",
|
||||
},
|
||||
STRUCTURED_ATTRS_1: {
|
||||
DATA_SCHEMA: "dataschema",
|
||||
DATA_BASE64: "data_base64",
|
||||
},
|
||||
});
|
||||
|
||||
export default CONSTANTS;
|
|
@ -0,0 +1,168 @@
|
|||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
import { CloudEventV1, validateV1, CloudEventV1Attributes } from "./v1";
|
||||
import { CloudEventV03, validateV03, CloudEventV03Attributes } from "./v03";
|
||||
import { ValidationError, isBinary, asBase64 } from "./validation";
|
||||
import CONSTANTS from "../constants";
|
||||
import { isString } from "util";
|
||||
|
||||
/**
|
||||
* An enum representing the CloudEvent specification version
|
||||
*/
|
||||
export const enum Version {
|
||||
V1 = "1.0",
|
||||
V03 = "0.3",
|
||||
}
|
||||
|
||||
/**
|
||||
* A CloudEvent describes event data in common formats to provide
|
||||
* interoperability across services, platforms and systems.
|
||||
* @see https://github.com/cloudevents/spec/blob/v1.0/spec.md
|
||||
*/
|
||||
export class CloudEvent implements CloudEventV1, CloudEventV03 {
|
||||
id: string;
|
||||
type: string;
|
||||
source: string;
|
||||
specversion: Version;
|
||||
datacontenttype?: string;
|
||||
dataschema?: string;
|
||||
subject?: string;
|
||||
#_time?: string | Date;
|
||||
#_data?: Record<string, unknown | string | number | boolean> | string | number | boolean | null | unknown;
|
||||
data_base64?: string;
|
||||
|
||||
// Extensions should not exist as it's own object, but instead
|
||||
// exist as properties on the event as siblings of the others
|
||||
[key: string]: unknown;
|
||||
|
||||
// V03 deprecated attributes
|
||||
schemaurl?: string;
|
||||
datacontentencoding?: string;
|
||||
|
||||
constructor(event: CloudEventV1 | CloudEventV1Attributes | CloudEventV03 | CloudEventV03Attributes) {
|
||||
// copy the incoming event so that we can delete properties as we go
|
||||
// everything left after we have deleted know properties becomes an extension
|
||||
const properties = { ...event };
|
||||
|
||||
this.id = (properties.id as string) || uuidv4();
|
||||
delete properties.id;
|
||||
|
||||
this.type = properties.type;
|
||||
delete properties.type;
|
||||
|
||||
this.source = properties.source;
|
||||
delete properties.source;
|
||||
|
||||
this.specversion = (properties.specversion as Version) || Version.V1;
|
||||
delete properties.specversion;
|
||||
|
||||
this.datacontenttype = properties.datacontenttype;
|
||||
delete properties.datacontenttype;
|
||||
|
||||
this.subject = properties.subject;
|
||||
delete properties.subject;
|
||||
|
||||
this.#_time = properties.time;
|
||||
delete properties.time;
|
||||
|
||||
this.datacontentencoding = properties.datacontentencoding as string;
|
||||
delete properties.datacontentencoding;
|
||||
|
||||
this.dataschema = properties.dataschema as string;
|
||||
delete properties.dataschema;
|
||||
|
||||
this.data_base64 = properties.data_base64 as string;
|
||||
delete properties.data_base64;
|
||||
|
||||
this.schemaurl = properties.schemaurl as string;
|
||||
delete properties.schemaurl;
|
||||
|
||||
this._setData(properties.data);
|
||||
delete properties.data;
|
||||
|
||||
// Make sure time has a default value and whatever is provided is formatted
|
||||
if (!this.#_time) {
|
||||
this.#_time = new Date().toISOString();
|
||||
} else if (this.#_time instanceof Date) {
|
||||
this.#_time = this.#_time.toISOString();
|
||||
}
|
||||
|
||||
// sanity checking
|
||||
if (this.specversion === Version.V1 && this.schemaurl) {
|
||||
throw new TypeError("cannot set schemaurl on version 1.0 event");
|
||||
} else if (this.specversion === Version.V03 && this.dataschema) {
|
||||
throw new TypeError("cannot set dataschema on version 0.3 event");
|
||||
}
|
||||
|
||||
// finally process any remaining properties - these are extensions
|
||||
for (const [key, value] of Object.entries(properties)) {
|
||||
this[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
get time(): string | Date {
|
||||
return this.#_time as string | Date;
|
||||
}
|
||||
|
||||
set time(val: string | Date) {
|
||||
this.#_time = new Date(val).toISOString();
|
||||
}
|
||||
|
||||
get data(): unknown {
|
||||
if (
|
||||
this.datacontenttype === CONSTANTS.MIME_JSON &&
|
||||
!(this.datacontentencoding === CONSTANTS.ENCODING_BASE64) &&
|
||||
isString(this.#_data)
|
||||
) {
|
||||
return JSON.parse(this.#_data as string);
|
||||
} else if (isBinary(this.#_data)) {
|
||||
return asBase64(this.#_data as Uint32Array);
|
||||
}
|
||||
return this.#_data;
|
||||
}
|
||||
|
||||
set data(value: unknown) {
|
||||
this._setData(value);
|
||||
}
|
||||
|
||||
private _setData(value: unknown): void {
|
||||
if (isBinary(value)) {
|
||||
this.#_data = value;
|
||||
this.data_base64 = asBase64(value as Uint32Array);
|
||||
}
|
||||
this.#_data = value;
|
||||
}
|
||||
|
||||
toJSON(): Record<string, unknown> {
|
||||
const event = { ...this };
|
||||
event.time = this.time;
|
||||
event.data = this.data;
|
||||
return event;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return JSON.stringify(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates this CloudEvent against the schema
|
||||
* @throws if the CloudEvent does not conform to the schema
|
||||
* @return {boolean} true if this event is valid
|
||||
*/
|
||||
public validate(): boolean {
|
||||
try {
|
||||
if (this.specversion === Version.V1) {
|
||||
return validateV1(this);
|
||||
} else if (this.specversion === Version.V03) {
|
||||
return validateV03(this);
|
||||
}
|
||||
throw new ValidationError("invalid payload");
|
||||
} catch (e) {
|
||||
if (e instanceof ValidationError) {
|
||||
throw e;
|
||||
} else {
|
||||
throw new ValidationError("invalid payload", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
export * from "./cloudevent";
|
||||
export * from "./validation";
|
||||
export * from "./v1";
|
||||
export * from "./v03";
|
|
@ -1,5 +1,3 @@
|
|||
import Extensions from "../extensions";
|
||||
|
||||
/**
|
||||
* The object interface for CloudEvents 0.3.
|
||||
* @see https://github.com/cloudevents/spec/blob/v0.3/spec.md
|
||||
|
@ -71,7 +69,7 @@ export interface CloudEventV03Attributes {
|
|||
* media type. When the data field's effective data type is not String, this attribute
|
||||
* MUST NOT be set and MUST be ignored when set.
|
||||
*/
|
||||
dataContentEncoding?: string;
|
||||
datacontentencoding?: string;
|
||||
|
||||
/**
|
||||
* [OPTIONAL] Content type of `data` value. This attribute enables `data` to
|
||||
|
@ -84,13 +82,13 @@ export interface CloudEventV03Attributes {
|
|||
* example, the JSON event format defines the relationship in
|
||||
* [section 3.1](./json-format.md#31-handling-of-data).
|
||||
*/
|
||||
dataContentType?: string;
|
||||
datacontenttype?: string;
|
||||
/**
|
||||
* [OPTIONAL] A link to the schema that the data attribute adheres to.
|
||||
* Incompatible changes to the schema SHOULD be reflected by a different URL.
|
||||
* If present, MUST be a non-empty URI.
|
||||
*/
|
||||
schemaURL?: string;
|
||||
schemaurl?: string;
|
||||
/**
|
||||
* [OPTIONAL] This describes the subject of the event in the context of the
|
||||
* event producer (identified by `source`). In publish-subscribe scenarios, a
|
||||
|
@ -127,13 +125,9 @@ export interface CloudEventV03Attributes {
|
|||
* specified by the datacontenttype attribute (e.g. application/json), and adheres
|
||||
* to the dataschema format when those respective attributes are present.
|
||||
*/
|
||||
// tslint:disable-next-line:no-any
|
||||
data?: any | string | number | boolean | null;
|
||||
data?: Record<string, unknown | string | number | boolean> | string | number | boolean | null | unknown;
|
||||
/**
|
||||
* [OPTIONAL] CloudEvents extension attributes.
|
||||
*/
|
||||
extensions?: Extensions;
|
||||
|
||||
// tslint:disable-next-line:no-any
|
||||
[key: string]: any;
|
||||
[key: string]: unknown;
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export * from "./cloudevent";
|
||||
export * from "./spec";
|
||||
export * from "./schema";
|
|
@ -0,0 +1,74 @@
|
|||
const schemaV03 = {
|
||||
$ref: "#/definitions/event",
|
||||
definitions: {
|
||||
specversion: {
|
||||
const: "0.3",
|
||||
},
|
||||
datacontenttype: {
|
||||
type: "string",
|
||||
},
|
||||
data: {
|
||||
type: ["object", "string"],
|
||||
},
|
||||
event: {
|
||||
properties: {
|
||||
specversion: {
|
||||
$ref: "#/definitions/specversion",
|
||||
},
|
||||
datacontenttype: {
|
||||
$ref: "#/definitions/datacontenttype",
|
||||
},
|
||||
data: {
|
||||
$ref: "#/definitions/data",
|
||||
},
|
||||
id: {
|
||||
$ref: "#/definitions/id",
|
||||
},
|
||||
time: {
|
||||
$ref: "#/definitions/time",
|
||||
},
|
||||
schemaurl: {
|
||||
$ref: "#/definitions/schemaurl",
|
||||
},
|
||||
subject: {
|
||||
$ref: "#/definitions/subject",
|
||||
},
|
||||
type: {
|
||||
$ref: "#/definitions/type",
|
||||
},
|
||||
source: {
|
||||
$ref: "#/definitions/source",
|
||||
},
|
||||
},
|
||||
required: ["specversion", "id", "type", "source"],
|
||||
type: "object",
|
||||
},
|
||||
id: {
|
||||
type: "string",
|
||||
minLength: 1,
|
||||
},
|
||||
time: {
|
||||
format: "date-time",
|
||||
type: "string",
|
||||
},
|
||||
schemaurl: {
|
||||
type: "string",
|
||||
format: "uri-reference",
|
||||
},
|
||||
subject: {
|
||||
type: "string",
|
||||
minLength: 1,
|
||||
},
|
||||
type: {
|
||||
type: "string",
|
||||
minLength: 1,
|
||||
},
|
||||
source: {
|
||||
format: "uri-reference",
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
type: "object",
|
||||
};
|
||||
|
||||
export { schemaV03 as schema };
|
|
@ -0,0 +1,42 @@
|
|||
import { v4 as uuidv4 } from "uuid";
|
||||
import Ajv from "ajv";
|
||||
import { ValidationError, isBase64 } from "../validation";
|
||||
import { CloudEventV03, CloudEventV03Attributes } from "./cloudevent";
|
||||
import { CloudEvent } from "../..";
|
||||
import { schema } from "./schema";
|
||||
import CONSTANTS from "../../constants";
|
||||
|
||||
const ajv = new Ajv({ extendRefs: true });
|
||||
const isValidAgainstSchema = ajv.compile(schema);
|
||||
|
||||
export function createV03(attributes: CloudEventV03Attributes): CloudEventV03 {
|
||||
const event: CloudEventV03 = {
|
||||
specversion: schema.definitions.specversion.const,
|
||||
id: uuidv4(),
|
||||
time: new Date().toISOString(),
|
||||
...attributes,
|
||||
};
|
||||
return new CloudEvent(event);
|
||||
}
|
||||
|
||||
export function validateV03(event: CloudEventV03): boolean {
|
||||
if (!isValidAgainstSchema(event)) {
|
||||
throw new ValidationError("invalid payload", isValidAgainstSchema.errors);
|
||||
}
|
||||
return checkDataContentEncoding(event);
|
||||
}
|
||||
|
||||
function checkDataContentEncoding(event: CloudEventV03): boolean {
|
||||
if (event.datacontentencoding) {
|
||||
// we only support base64
|
||||
const encoding = event.datacontentencoding.toLocaleLowerCase();
|
||||
if (encoding !== CONSTANTS.ENCODING_BASE64) {
|
||||
throw new ValidationError("invalid payload", [`Unsupported content encoding: ${encoding}`]);
|
||||
} else {
|
||||
if (!isBase64(event.data)) {
|
||||
throw new ValidationError("invalid payload", [`Invalid content encoding of data: ${event.data}`]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
|
@ -1,4 +1,3 @@
|
|||
import Extensions from "../extensions";
|
||||
/**
|
||||
* The object interface for CloudEvents 1.0.
|
||||
* @see https://github.com/cloudevents/spec/blob/v1.0/spec.md
|
||||
|
@ -75,7 +74,7 @@ export interface CloudEventV1Attributes {
|
|||
* example, the JSON event format defines the relationship in
|
||||
* [section 3.1](./json-format.md#31-handling-of-data).
|
||||
*/
|
||||
dataContentType?: string;
|
||||
datacontenttype?: string;
|
||||
/**
|
||||
* [OPTIONAL] Identifies the schema that `data` adheres to. Incompatible
|
||||
* changes to the schema SHOULD be reflected by a different URI. See
|
||||
|
@ -83,7 +82,7 @@ export interface CloudEventV1Attributes {
|
|||
* for more information.
|
||||
* If present, MUST be a non-empty URI.
|
||||
*/
|
||||
dataSchema?: string;
|
||||
dataschema?: string;
|
||||
/**
|
||||
* [OPTIONAL] This describes the subject of the event in the context of the
|
||||
* event producer (identified by `source`). In publish-subscribe scenarios, a
|
||||
|
@ -120,13 +119,17 @@ export interface CloudEventV1Attributes {
|
|||
* specified by the datacontenttype attribute (e.g. application/json), and adheres
|
||||
* to the dataschema format when those respective attributes are present.
|
||||
*/
|
||||
// tslint:disable-next-line:no-any
|
||||
data?: any | string | number | boolean | null;
|
||||
data?: Record<string, unknown | string | number | boolean> | string | number | boolean | null | unknown;
|
||||
|
||||
/**
|
||||
* [OPTIONAL] The event payload encoded as base64 data. This is used when the
|
||||
* data is in binary form.
|
||||
* @see https://github.com/cloudevents/spec/blob/v1.0/json-format.md#31-handling-of-data
|
||||
*/
|
||||
data_base64?: string;
|
||||
|
||||
/**
|
||||
* [OPTIONAL] CloudEvents extension attributes.
|
||||
*/
|
||||
extensions?: Extensions;
|
||||
|
||||
// tslint:disable-next-line:no-any
|
||||
[key: string]: any;
|
||||
[key: string]: unknown;
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export * from "./cloudevent";
|
||||
export * from "./spec";
|
||||
export * from "./schema";
|
|
@ -0,0 +1,82 @@
|
|||
const schemaV1 = {
|
||||
$ref: "#/definitions/event",
|
||||
definitions: {
|
||||
specversion: {
|
||||
type: "string",
|
||||
minLength: 1,
|
||||
const: "1.0",
|
||||
},
|
||||
datacontenttype: {
|
||||
type: "string",
|
||||
},
|
||||
data: {
|
||||
type: ["object", "string"],
|
||||
},
|
||||
data_base64: {
|
||||
type: "string",
|
||||
},
|
||||
event: {
|
||||
properties: {
|
||||
specversion: {
|
||||
$ref: "#/definitions/specversion",
|
||||
},
|
||||
datacontenttype: {
|
||||
$ref: "#/definitions/datacontenttype",
|
||||
},
|
||||
data: {
|
||||
$ref: "#/definitions/data",
|
||||
},
|
||||
data_base64: {
|
||||
$ref: "#/definitions/data_base64",
|
||||
},
|
||||
id: {
|
||||
$ref: "#/definitions/id",
|
||||
},
|
||||
time: {
|
||||
$ref: "#/definitions/time",
|
||||
},
|
||||
dataschema: {
|
||||
$ref: "#/definitions/dataschema",
|
||||
},
|
||||
subject: {
|
||||
$ref: "#/definitions/subject",
|
||||
},
|
||||
type: {
|
||||
$ref: "#/definitions/type",
|
||||
},
|
||||
source: {
|
||||
$ref: "#/definitions/source",
|
||||
},
|
||||
},
|
||||
required: ["specversion", "id", "type", "source"],
|
||||
type: "object",
|
||||
},
|
||||
id: {
|
||||
type: "string",
|
||||
minLength: 1,
|
||||
},
|
||||
time: {
|
||||
format: "date-time",
|
||||
type: "string",
|
||||
},
|
||||
dataschema: {
|
||||
type: "string",
|
||||
format: "uri",
|
||||
},
|
||||
subject: {
|
||||
type: "string",
|
||||
minLength: 1,
|
||||
},
|
||||
type: {
|
||||
type: "string",
|
||||
minLength: 1,
|
||||
},
|
||||
source: {
|
||||
format: "uri-reference",
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
type: "object",
|
||||
};
|
||||
|
||||
export { schemaV1 };
|
|
@ -0,0 +1,28 @@
|
|||
import Ajv from "ajv";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
import { CloudEvent } from "../cloudevent";
|
||||
import { CloudEventV1, CloudEventV1Attributes } from "./cloudevent";
|
||||
import { ValidationError } from "../validation";
|
||||
|
||||
import { schemaV1 } from "./schema";
|
||||
|
||||
const ajv = new Ajv({ extendRefs: true });
|
||||
const isValidAgainstSchema = ajv.compile(schemaV1);
|
||||
|
||||
export function createV1(attributes: CloudEventV1Attributes): CloudEventV1 {
|
||||
const event: CloudEventV1 = {
|
||||
specversion: schemaV1.definitions.specversion.const,
|
||||
id: uuidv4(),
|
||||
time: new Date().toISOString(),
|
||||
...attributes,
|
||||
};
|
||||
return new CloudEvent(event);
|
||||
}
|
||||
|
||||
export function validateV1(event: CloudEventV1): boolean {
|
||||
if (!isValidAgainstSchema(event)) {
|
||||
throw new ValidationError("invalid payload", isValidAgainstSchema.errors);
|
||||
}
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./is";
|
||||
export * from "./validation_error";
|
|
@ -0,0 +1,87 @@
|
|||
const isString = (v: unknown): boolean => typeof v === "string";
|
||||
const isObject = (v: unknown): boolean => typeof v === "object";
|
||||
const isDefined = (v: unknown): boolean => v && typeof v !== "undefined";
|
||||
|
||||
const isBoolean = (v: unknown): boolean => typeof v === "boolean";
|
||||
const isInteger = (v: unknown): boolean => Number.isInteger(v as number);
|
||||
const isDate = (v: unknown): boolean => v instanceof Date;
|
||||
const isBinary = (v: unknown): boolean => v instanceof Uint32Array;
|
||||
|
||||
const isStringOrThrow = (v: unknown, t: Error): boolean =>
|
||||
isString(v)
|
||||
? true
|
||||
: (() => {
|
||||
throw t;
|
||||
})();
|
||||
|
||||
const isDefinedOrThrow = (v: unknown, t: Error): boolean =>
|
||||
isDefined(v)
|
||||
? true
|
||||
: (() => {
|
||||
throw t;
|
||||
})();
|
||||
|
||||
const isStringOrObjectOrThrow = (v: unknown, t: Error): boolean =>
|
||||
isString(v)
|
||||
? true
|
||||
: isObject(v)
|
||||
? true
|
||||
: (() => {
|
||||
throw t;
|
||||
})();
|
||||
|
||||
const equalsOrThrow = (v1: unknown, v2: unknown, t: Error): boolean =>
|
||||
v1 === v2
|
||||
? true
|
||||
: (() => {
|
||||
throw t;
|
||||
})();
|
||||
|
||||
const isBase64 = (value: unknown): boolean => Buffer.from(value as string, "base64").toString("base64") === value;
|
||||
|
||||
const isBuffer = (value: unknown): boolean => value instanceof Buffer;
|
||||
|
||||
const asBuffer = (value: string | Buffer | Uint32Array): Buffer =>
|
||||
isBinary(value)
|
||||
? Buffer.from(value as string)
|
||||
: isBuffer(value)
|
||||
? (value as Buffer)
|
||||
: (() => {
|
||||
throw new TypeError("is not buffer or a valid binary");
|
||||
})();
|
||||
|
||||
const asBase64 = (value: string | Buffer | Uint32Array): string => asBuffer(value).toString("base64");
|
||||
|
||||
const clone = (o: Record<string, unknown>): Record<string, unknown> => JSON.parse(JSON.stringify(o));
|
||||
|
||||
const isJsonContentType = (contentType: string) => contentType && contentType.match(/(json)/i);
|
||||
|
||||
const asData = (data: unknown, contentType: string): string => {
|
||||
// pattern matching alike
|
||||
const maybeJson =
|
||||
isString(data) && !isBase64(data) && isJsonContentType(contentType) ? JSON.parse(data as string) : data;
|
||||
|
||||
return isBinary(maybeJson) ? asBase64(maybeJson) : maybeJson;
|
||||
};
|
||||
|
||||
const isValidType = (v: boolean | number | string | Date | Uint32Array): boolean =>
|
||||
isBoolean(v) || isInteger(v) || isString(v) || isDate(v) || isBinary(v);
|
||||
|
||||
export {
|
||||
isString,
|
||||
isStringOrThrow,
|
||||
isObject,
|
||||
isDefined,
|
||||
isBoolean,
|
||||
isInteger,
|
||||
isDate,
|
||||
isBinary,
|
||||
isDefinedOrThrow,
|
||||
isStringOrObjectOrThrow,
|
||||
isValidType,
|
||||
equalsOrThrow,
|
||||
isBase64,
|
||||
clone,
|
||||
asData,
|
||||
asBase64,
|
||||
};
|
|
@ -0,0 +1,14 @@
|
|||
import { ErrorObject } from "ajv";
|
||||
|
||||
/**
|
||||
* An Error class that will be thrown when a CloudEvent
|
||||
* cannot be properly validated against a specification.
|
||||
*/
|
||||
export class ValidationError extends TypeError {
|
||||
errors?: string[] | ErrorObject[] | null;
|
||||
|
||||
constructor(message: string, errors?: string[] | ErrorObject[] | null) {
|
||||
super(message);
|
||||
this.errors = errors ? errors : [];
|
||||
}
|
||||
}
|
38
src/index.ts
38
src/index.ts
|
@ -1,11 +1,31 @@
|
|||
import { CloudEvent } from "./lib/cloudevent";
|
||||
import { HTTPReceiver } from "./lib/bindings/http/http_receiver";
|
||||
import { HTTPEmitter } from "./lib/bindings/http/http_emitter";
|
||||
const Constants = require("./lib/bindings/http/constants");
|
||||
|
||||
export = {
|
||||
import {
|
||||
CloudEvent,
|
||||
HTTPReceiver,
|
||||
HTTPEmitter,
|
||||
Constants
|
||||
CloudEventV03,
|
||||
CloudEventV03Attributes,
|
||||
CloudEventV1,
|
||||
CloudEventV1Attributes,
|
||||
ValidationError,
|
||||
Version,
|
||||
} from "./event";
|
||||
|
||||
import { Emitter, Receiver, Mode, Protocol, TransportOptions } from "./transport";
|
||||
import { Headers, headersFor } from "./transport/http/headers";
|
||||
|
||||
export {
|
||||
// From event
|
||||
CloudEvent,
|
||||
CloudEventV03,
|
||||
CloudEventV03Attributes,
|
||||
CloudEventV1,
|
||||
CloudEventV1Attributes,
|
||||
Version,
|
||||
ValidationError,
|
||||
// From transport
|
||||
Emitter,
|
||||
Receiver,
|
||||
Mode,
|
||||
Protocol,
|
||||
TransportOptions,
|
||||
Headers,
|
||||
headersFor,
|
||||
};
|
||||
|
|
|
@ -1,88 +0,0 @@
|
|||
const { default: Axios } = require("axios");
|
||||
const EmitterV1 = require("./v1").BinaryEmitter;
|
||||
const EmitterV3 = require("./v03").BinaryEmitter;
|
||||
|
||||
const {
|
||||
HEADERS,
|
||||
BINARY_HEADERS_03,
|
||||
BINARY_HEADERS_1,
|
||||
HEADER_CONTENT_TYPE,
|
||||
DEFAULT_CONTENT_TYPE,
|
||||
DATA_ATTRIBUTE,
|
||||
SPEC_V1,
|
||||
SPEC_V03
|
||||
} = require("./constants");
|
||||
|
||||
const defaults = {
|
||||
[HEADERS]: {
|
||||
[HEADER_CONTENT_TYPE]: DEFAULT_CONTENT_TYPE
|
||||
},
|
||||
method: "POST"
|
||||
};
|
||||
|
||||
/**
|
||||
* A class to emit binary CloudEvents over HTTP.
|
||||
*/
|
||||
class BinaryHTTPEmitter {
|
||||
/**
|
||||
* Create a new {BinaryHTTPEmitter} for the provided CloudEvent specification version.
|
||||
* Once an instance is created for a given spec version, it may only be used to send
|
||||
* events for that version.
|
||||
* Default version is 1.0
|
||||
* @param {string} version - the CloudEvent HTTP specification version.
|
||||
* Default: 1.0
|
||||
*/
|
||||
constructor(version) {
|
||||
if (version === SPEC_V1) {
|
||||
this.headerParserMap = EmitterV1;
|
||||
this.extensionPrefix = BINARY_HEADERS_1.EXTENSIONS_PREFIX;
|
||||
} else if (version === SPEC_V03) {
|
||||
this.headerParserMap = EmitterV3;
|
||||
this.extensionPrefix = BINARY_HEADERS_03.EXTENSIONS_PREFIX;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends this cloud event to a receiver over HTTP.
|
||||
*
|
||||
* @param {Object} options The configuration options for this event. Options
|
||||
* provided other than `url` will be passed along to Node.js `http.request`.
|
||||
* https://nodejs.org/api/http.html#http_http_request_options_callback
|
||||
* @param {URL} options.url The HTTP/S url that should receive this event
|
||||
* @param {Object} cloudevent the CloudEvent to be sent
|
||||
* @returns {Promise} Promise with an eventual response from the receiver
|
||||
*/
|
||||
async emit(options, cloudevent) {
|
||||
const config = { ...options, ...defaults };
|
||||
const headers = config[HEADERS];
|
||||
|
||||
this.headerParserMap.forEach((parser, getterName) => {
|
||||
const value = cloudevent[getterName];
|
||||
if (value) {
|
||||
headers[parser.headerName] = parser.parse(value);
|
||||
}
|
||||
});
|
||||
|
||||
// Set the cloudevent payload
|
||||
const formatted = cloudevent.format();
|
||||
let data = formatted.data;
|
||||
data = (formatted.data_base64 ? formatted.data_base64 : data);
|
||||
|
||||
// Have extensions?
|
||||
const exts = cloudevent.getExtensions();
|
||||
Object.keys(exts)
|
||||
.filter((ext) => Object.hasOwnProperty.call(exts, ext))
|
||||
.forEach((ext) => {
|
||||
headers[this.extensionPrefix + ext] = exts[ext];
|
||||
});
|
||||
|
||||
config[DATA_ATTRIBUTE] = data;
|
||||
config[HEADERS] = headers;
|
||||
|
||||
// Return the Promise
|
||||
// @ts-ignore Types of property 'url' are incompatible. Type 'URL' is not assignable to type 'string'.
|
||||
return Axios.request(config);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BinaryHTTPEmitter;
|
|
@ -1,42 +0,0 @@
|
|||
const { default: Axios } = require("axios");
|
||||
|
||||
/** @typedef {import("../../cloudevent")} CloudEvent */
|
||||
|
||||
const {
|
||||
DATA_ATTRIBUTE,
|
||||
DEFAULT_CE_CONTENT_TYPE,
|
||||
HEADERS,
|
||||
HEADER_CONTENT_TYPE
|
||||
} = require("./constants");
|
||||
|
||||
const defaults = {
|
||||
[HEADERS]: {
|
||||
[HEADER_CONTENT_TYPE]: DEFAULT_CE_CONTENT_TYPE
|
||||
},
|
||||
method: "POST"
|
||||
};
|
||||
|
||||
/**
|
||||
* A class for sending {CloudEvent} instances over HTTP.
|
||||
*/
|
||||
class StructuredHTTPEmitter {
|
||||
// TODO: Do we really need a class here? There is no state maintenance
|
||||
|
||||
/**
|
||||
* Sends the event over HTTP
|
||||
* @param {Object} options The configuration options for this event. Options
|
||||
* provided will be passed along to Node.js `http.request()`.
|
||||
* https://nodejs.org/api/http.html#http_http_request_options_callback
|
||||
* @param {URL} options.url The HTTP/S url that should receive this event
|
||||
* @param {CloudEvent} cloudevent The CloudEvent to be sent
|
||||
* @returns {Promise} Promise with an eventual response from the receiver
|
||||
*/
|
||||
async emit(options, cloudevent) {
|
||||
const config = { ...defaults, ...options };
|
||||
config[DATA_ATTRIBUTE] = cloudevent.format();
|
||||
// @ts-ignore Types of property 'url' are incompatible. Type 'URL' is not assignable to type 'string'.
|
||||
return Axios.request(config);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = StructuredHTTPEmitter;
|
|
@ -1,109 +0,0 @@
|
|||
import { CloudEvent } from "../../cloudevent";
|
||||
|
||||
const BinaryHTTPEmitter = require("./emitter_binary.js");
|
||||
const StructuredEmitter = require("./emitter_structured.js");
|
||||
const EmitterV1 = require("./v1").BinaryEmitter;
|
||||
const EmitterV03 = require("./v03").BinaryEmitter;
|
||||
|
||||
/** @typedef {import("../../cloudevent")} CloudEvent */
|
||||
|
||||
const {
|
||||
SPEC_V03,
|
||||
SPEC_V1
|
||||
} = require("./constants");
|
||||
|
||||
/**
|
||||
* A class which is capable of sending binary and structured events using
|
||||
* the CloudEvents HTTP Protocol Binding specification.
|
||||
*
|
||||
* @see https://github.com/cloudevents/spec/blob/v1.0/http-protocol-binding.md
|
||||
* @see https://github.com/cloudevents/spec/blob/v1.0/http-protocol-binding.md#13-content-modes
|
||||
*/
|
||||
export class HTTPEmitter {
|
||||
url: URL | string;
|
||||
binary: any;
|
||||
structured: any;
|
||||
static headers: Function;
|
||||
|
||||
/**
|
||||
* Creates a new instance of {HTTPEmitter}. The default emitter uses the 1.0
|
||||
* protocol specification in binary mode.
|
||||
*
|
||||
* @param {Object} [options] The configuration options for this event emitter
|
||||
* @param {URL} options.url The endpoint that will receive the sent events.
|
||||
* @param {string} [options.version] The HTTP binding specification version. Default: "1.0"
|
||||
* @throws {TypeError} if no options.url is provided or an unknown specification version is provided.
|
||||
*/
|
||||
constructor({ url = "", version = SPEC_V1 }) {
|
||||
if (version !== SPEC_V03 && version !== SPEC_V1) {
|
||||
throw new TypeError(
|
||||
`Unknown CloudEvent specification version: ${version}`);
|
||||
}
|
||||
if (!url) {
|
||||
throw new TypeError("A default endpoint URL is required for a CloudEvent emitter");
|
||||
}
|
||||
this.binary = new BinaryHTTPEmitter(version);
|
||||
this.structured = new StructuredEmitter();
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the {CloudEvent} to an event receiver over HTTP POST
|
||||
*
|
||||
* @param {CloudEvent} event the CloudEvent to be sent
|
||||
* @param {Object} [options] The configuration options for this event. Options
|
||||
* provided will be passed along to Node.js `http.request()`.
|
||||
* https://nodejs.org/api/http.html#http_http_request_options_callback
|
||||
* @param {URL} [options.url] The HTTP/S url that should receive this event.
|
||||
* The URL is optional if one was provided when this emitter was constructed.
|
||||
* In that case, it will be used as the recipient endpoint. The endpoint can
|
||||
* be overridden by providing a URL here.
|
||||
* @param {string} [options.mode] the message mode for sending this event.
|
||||
* Possible values are "binary" and "structured". Default: structured
|
||||
* @returns {Promise} Promise with an eventual response from the receiver
|
||||
*/
|
||||
send(event: CloudEvent, { url = "", mode = "binary", ...httpOpts } = {}) {
|
||||
// @ts-ignore Type 'URL | undefined' is not assignable to type 'undefined'. Type 'URL' is not assignable to type 'undefined'.ts(2322)
|
||||
if (!url) { url = this.url; }
|
||||
// @ts-ignore Property 'url' does not exist on type '{}'
|
||||
httpOpts.url = url;
|
||||
if (mode === "binary") {
|
||||
// @ts-ignore Property 'url' is missing in type '{}' but required in type '{ url: URL; }'.
|
||||
return this.binary.emit(httpOpts, event);
|
||||
} else if (mode === "structured") {
|
||||
return this.structured.emit(httpOpts, event);
|
||||
}
|
||||
throw new TypeError(`Unknown transport mode ${mode}.`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the HTTP headers that will be sent for this event when the HTTP transmission
|
||||
* mode is "binary". Events sent over HTTP in structured mode only have a single CE header
|
||||
* and that is "ce-id", corresponding to the event ID.
|
||||
* @param {CloudEvent} event a CloudEvent
|
||||
* @param {string} [version] spec version number - default 1.0
|
||||
* @returns {Object} the headers that will be sent for the event
|
||||
*/
|
||||
function headers(event: CloudEvent, version: string = SPEC_V1) {
|
||||
const headers = {};
|
||||
let headerMap;
|
||||
if (version === SPEC_V1) {
|
||||
headerMap = EmitterV1;
|
||||
} else if (version === SPEC_V03) {
|
||||
headerMap = EmitterV03;
|
||||
}
|
||||
headerMap.forEach((parser: any, getterName: string) => {
|
||||
// @ts-ignore No index signature with a parameter of type 'string' was found on type 'CloudEvent'.
|
||||
const value = event[getterName];
|
||||
if (value) {
|
||||
// @ts-ignore Element implicitly has an 'any' type because expression of type 'any'
|
||||
// can't be used to index type '{ } '.
|
||||
headers[parser.headerName] = parser.parse(value);
|
||||
}
|
||||
});
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
HTTPEmitter.headers = headers;
|
|
@ -1,88 +0,0 @@
|
|||
import BinaryReceiver = require("./receiver_binary.js");
|
||||
import StructuredReceiver = require("./receiver_structured.js");
|
||||
import ValidationError = require("./validation/validation_error.js");
|
||||
import { CloudEvent } from "../../cloudevent.js";
|
||||
const { BINARY, STRUCTURED, SPEC_V03, SPEC_V1, HEADER_CONTENT_TYPE, MIME_CE, BINARY_HEADERS_1, DEFAULT_SPEC_VERSION_HEADER } = require("./constants");
|
||||
|
||||
/** @typedef {import("../../cloudevent")} CloudEvent */
|
||||
/**
|
||||
* A class to receive a CloudEvent from an HTTP POST request.
|
||||
*/
|
||||
export class HTTPReceiver {
|
||||
receivers: {
|
||||
v1: {
|
||||
structured: any,
|
||||
binary: any,
|
||||
[key: string]: any
|
||||
},
|
||||
v03: {
|
||||
structured: any,
|
||||
binary: any,
|
||||
[key: string]: any
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create an instance of an HTTPReceiver to accept incoming CloudEvents.
|
||||
*/
|
||||
constructor() {
|
||||
this.receivers = {
|
||||
v1: {
|
||||
structured: new StructuredReceiver(SPEC_V1),
|
||||
binary: new BinaryReceiver(SPEC_V1)
|
||||
},
|
||||
v03: {
|
||||
structured: new StructuredReceiver(SPEC_V03),
|
||||
binary: new BinaryReceiver(SPEC_V03)
|
||||
}
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Acceptor for an incoming HTTP CloudEvent POST. Can process
|
||||
* binary and structured incoming CloudEvents.
|
||||
*
|
||||
* @param {Object} headers HTTP headers keyed by header name ("Content-Type")
|
||||
* @param {Object|JSON} body The body of the HTTP request
|
||||
* @return {CloudEvent} A new {CloudEvent} instance
|
||||
*/
|
||||
accept(headers: {}, body: {}) : CloudEvent {
|
||||
const mode: string = getMode(headers);
|
||||
const version = getVersion(mode, headers, body);
|
||||
switch (version) {
|
||||
case SPEC_V1:
|
||||
return this.receivers.v1[mode].parse(body, headers);
|
||||
case SPEC_V03:
|
||||
return this.receivers.v03[mode].parse(body, headers);
|
||||
default:
|
||||
console.error(`Unknown spec version ${version}. Default to ${SPEC_V1}`);
|
||||
return this.receivers.v1[mode].parse(body, headers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getMode(headers: { [key: string]: string }) {
|
||||
const contentType = headers[HEADER_CONTENT_TYPE];
|
||||
if (contentType && contentType.startsWith(MIME_CE)) {
|
||||
return STRUCTURED;
|
||||
}
|
||||
if (headers[BINARY_HEADERS_1.ID]) {
|
||||
return BINARY;
|
||||
}
|
||||
throw new ValidationError("no cloud event detected");
|
||||
}
|
||||
|
||||
function getVersion(mode: string, headers: { [key: string]: string }, body: string | { specversion?: string }) {
|
||||
if (mode === BINARY) {
|
||||
// Check the headers for the version
|
||||
const versionHeader = headers[DEFAULT_SPEC_VERSION_HEADER];
|
||||
if (versionHeader) {
|
||||
return versionHeader;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// structured mode - the version is in the body
|
||||
return (typeof body === "string")
|
||||
? JSON.parse(body).specversion : body.specversion;
|
||||
}
|
||||
return SPEC_V1;
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
const ReceiverV1 = require("./v1/receiver_binary_1.js");
|
||||
const ReceiverV3 = require("./v03/receiver_binary_0_3.js");
|
||||
|
||||
const { SPEC_V03, SPEC_V1 } = require("./constants");
|
||||
const { check, parse } = require("./validation/binary.js");
|
||||
|
||||
/** @typedef {import("../../cloudevent")} CloudEvent */
|
||||
|
||||
/**
|
||||
* A class that receives binary CloudEvents over HTTP. This class can be used
|
||||
* if you know that all incoming events will be using binary transport. If
|
||||
* events can come as either binary or structured, use {HTTPReceiver}.
|
||||
*/
|
||||
class BinaryHTTPReceiver {
|
||||
/**
|
||||
* Creates a new BinaryHTTPReceiver to accept events over HTTP.
|
||||
*
|
||||
* @param {string} version the Cloud Event specification version to use. Default "1.0"
|
||||
*/
|
||||
constructor(version = SPEC_V1) {
|
||||
if (version === SPEC_V1) {
|
||||
this.receiver = new ReceiverV1();
|
||||
} else if (version === SPEC_V03) {
|
||||
this.receiver = new ReceiverV3();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks an incoming HTTP request to determine if it conforms to the
|
||||
* Cloud Event specification for this receiver.
|
||||
*
|
||||
* @param {Object} payload the HTTP request body
|
||||
* @param {Object} headers the HTTP request headers
|
||||
* @returns {boolean} true if the the provided payload and headers conform to the spec
|
||||
* @throws {ValidationError} if the event does not conform to the spec
|
||||
*/
|
||||
check(payload, headers) {
|
||||
return check(payload, headers, this.receiver);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an incoming HTTP request, converting it to a {CloudEvent}
|
||||
* instance if it conforms to the Cloud Event specification for this receiver.
|
||||
*
|
||||
* @param {Object} payload the HTTP request body
|
||||
* @param {Object} headers the HTTP request headers
|
||||
* @returns {CloudEvent} an instance of CloudEvent representing the incoming request
|
||||
* @throws {ValidationError} of the event does not conform to the spec
|
||||
*/
|
||||
parse(payload, headers) {
|
||||
return parse(payload, headers, this.receiver);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BinaryHTTPReceiver;
|
|
@ -1,55 +0,0 @@
|
|||
const ReceiverV1 = require("./v1/receiver_structured_1.js");
|
||||
const ReceiverV3 = require("./v03/receiver_structured_0_3.js");
|
||||
|
||||
const { SPEC_V03, SPEC_V1 } = require("./constants");
|
||||
const { check, parse } = require("./validation/structured.js");
|
||||
|
||||
/** @typedef {import("../../cloudevent")} CloudEvent */
|
||||
|
||||
/**
|
||||
* A utility class used to receive structured CloudEvents
|
||||
* over HTTP.
|
||||
* @see {StructuredReceiver}
|
||||
*/
|
||||
class StructuredHTTPReceiver {
|
||||
/**
|
||||
* Creates a new StructuredHTTPReceiver. Supports Cloud Events specification
|
||||
* versions 0.3 and 1.0 (default).
|
||||
*
|
||||
* @param {string} version the Cloud Events specification version. Default: 1.0.
|
||||
*/
|
||||
constructor(version = SPEC_V1) {
|
||||
if (version === SPEC_V1) {
|
||||
this.receiver = new ReceiverV1();
|
||||
} else if (version === SPEC_V03) {
|
||||
this.receiver = new ReceiverV3();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the provided payload and headers conform to the Cloud Events
|
||||
* specification version supported by this instance.
|
||||
*
|
||||
* @param {object} payload the cloud event data payload
|
||||
* @param {object} headers the HTTP headers received for this cloud event
|
||||
* @returns {boolean} true if the payload and header combination are valid
|
||||
* @throws {ValidationError} if the payload and header combination do not conform to the spec
|
||||
*/
|
||||
check(payload, headers) {
|
||||
return check(payload, headers, this.receiver);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new CloudEvent instance based on the provided payload and headers.
|
||||
*
|
||||
* @param {object} payload the cloud event data payload
|
||||
* @param {object} headers the HTTP headers received for this cloud event
|
||||
* @returns {CloudEvent} a new CloudEvent instance for the provided headers and payload
|
||||
* @throws {ValidationError} if the payload and header combination do not conform to the spec
|
||||
*/
|
||||
parse(payload, headers) {
|
||||
return parse(payload, headers, this.receiver);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = StructuredHTTPReceiver;
|
|
@ -1,35 +0,0 @@
|
|||
const {
|
||||
HEADER_CONTENT_TYPE,
|
||||
BINARY_HEADERS_03 : {
|
||||
CONTENT_ENCODING,
|
||||
SUBJECT,
|
||||
TYPE,
|
||||
SPEC_VERSION,
|
||||
SOURCE,
|
||||
ID,
|
||||
TIME,
|
||||
SCHEMA_URL
|
||||
}
|
||||
} = require("../constants");
|
||||
|
||||
function parser(header, parser = (v) => v) {
|
||||
return { headerName: header, parse: parser };
|
||||
}
|
||||
const passThroughParser = parser;
|
||||
|
||||
/**
|
||||
* A utility Map used to retrieve the header names for a CloudEvent
|
||||
* using the CloudEvent getter function.
|
||||
*/
|
||||
const headerMap = new Map();
|
||||
headerMap.set("dataContentType", passThroughParser(HEADER_CONTENT_TYPE));
|
||||
headerMap.set("dataContentEncoding", passThroughParser(CONTENT_ENCODING));
|
||||
headerMap.set("subject", passThroughParser(SUBJECT));
|
||||
headerMap.set("type", passThroughParser(TYPE));
|
||||
headerMap.set("specversion", passThroughParser(SPEC_VERSION));
|
||||
headerMap.set("source", passThroughParser(SOURCE));
|
||||
headerMap.set("id", passThroughParser(ID));
|
||||
headerMap.set("time", passThroughParser(TIME));
|
||||
headerMap.set("schemaURL", passThroughParser(SCHEMA_URL));
|
||||
|
||||
module.exports = headerMap;
|
|
@ -1,9 +0,0 @@
|
|||
const Spec = require("./spec_0_3.js");
|
||||
const BinaryEmitter = require("./emitter_binary_0_3.js");
|
||||
const StructuredEmitter = require("./receiver_structured_0_3.js");
|
||||
|
||||
module.exports = {
|
||||
Spec,
|
||||
BinaryEmitter,
|
||||
StructuredEmitter
|
||||
};
|
|
@ -1,70 +0,0 @@
|
|||
const {
|
||||
SPEC_V03,
|
||||
MIME_JSON,
|
||||
MIME_OCTET_STREAM,
|
||||
ENCODING_BASE64,
|
||||
HEADER_CONTENT_TYPE,
|
||||
BINARY_HEADERS_03
|
||||
} = require("../constants");
|
||||
const Spec = require("./spec_0_3.js");
|
||||
|
||||
const JSONParser = require("../../../formats/json/parser.js");
|
||||
const Base64Parser = require("../../../formats/base64.js");
|
||||
|
||||
const parserByType = {
|
||||
[MIME_JSON]: new JSONParser(),
|
||||
[MIME_OCTET_STREAM]: {
|
||||
parse(payload) { return payload; }
|
||||
}
|
||||
};
|
||||
|
||||
const parsersByEncoding = {
|
||||
null: parserByType,
|
||||
undefined: parserByType,
|
||||
[ENCODING_BASE64]: {
|
||||
[MIME_JSON]: new JSONParser(new Base64Parser()),
|
||||
[MIME_OCTET_STREAM]: {
|
||||
parse(payload) { return payload; }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const allowedContentTypes = [
|
||||
MIME_JSON, MIME_OCTET_STREAM
|
||||
];
|
||||
|
||||
const requiredHeaders = [
|
||||
BINARY_HEADERS_03.TYPE,
|
||||
BINARY_HEADERS_03.SPEC_VERSION,
|
||||
BINARY_HEADERS_03.SOURCE,
|
||||
BINARY_HEADERS_03.ID
|
||||
];
|
||||
|
||||
const passThroughParser = (v) => v;
|
||||
|
||||
const setterByHeader = {
|
||||
[BINARY_HEADERS_03.TYPE] : { name: "type", parser: passThroughParser },
|
||||
[BINARY_HEADERS_03.SPEC_VERSION] : { name: "specversion", parser: () => "0.3" },
|
||||
[BINARY_HEADERS_03.SOURCE] : { name: "source", parser: passThroughParser },
|
||||
[BINARY_HEADERS_03.ID] : { name: "id", parser: passThroughParser },
|
||||
[BINARY_HEADERS_03.TIME] : { name: "time", parser: (v) => new Date(Date.parse(v)) },
|
||||
[BINARY_HEADERS_03.SCHEMA_URL] : { name: "schemaURL", parser: passThroughParser },
|
||||
[HEADER_CONTENT_TYPE]: { name: "dataContentType", parser: passThroughParser },
|
||||
[BINARY_HEADERS_03.CONTENT_ENCONDING]: { name: "dataContentEncoding", parser: passThroughParser },
|
||||
[BINARY_HEADERS_03.SUBJECT] : { name: "subject", parser: passThroughParser }
|
||||
};
|
||||
|
||||
class Receiver {
|
||||
constructor() {
|
||||
this.parsersByEncoding = parsersByEncoding;
|
||||
this.setterByHeader = setterByHeader;
|
||||
this.allowedContentTypes = allowedContentTypes;
|
||||
this.requiredHeaders = requiredHeaders;
|
||||
this.extensionsPrefix = BINARY_HEADERS_03.EXTENSIONS_PREFIX;
|
||||
this.specversion = SPEC_V03;
|
||||
this.Spec = Spec;
|
||||
this.spec = new Spec();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Receiver;
|
|
@ -1,57 +0,0 @@
|
|||
const {
|
||||
MIME_JSON,
|
||||
MIME_CE_JSON,
|
||||
STRUCTURED_ATTRS_03 : {
|
||||
TYPE,
|
||||
SPEC_VERSION,
|
||||
SOURCE,
|
||||
ID,
|
||||
TIME,
|
||||
SCHEMA_URL,
|
||||
CONTENT_ENCODING,
|
||||
CONTENT_TYPE,
|
||||
SUBJECT,
|
||||
DATA
|
||||
}
|
||||
} = require("../constants");
|
||||
const Spec = require("./spec_0_3.js");
|
||||
const JSONParser = require("../../../formats/json/parser.js");
|
||||
|
||||
const jsonParserSpec = new JSONParser();
|
||||
const parserByMime = {
|
||||
[MIME_JSON]: jsonParserSpec,
|
||||
[MIME_CE_JSON]: jsonParserSpec
|
||||
};
|
||||
|
||||
const allowedContentTypes = [
|
||||
MIME_CE_JSON
|
||||
];
|
||||
|
||||
function parser(name, parser = (v) => v) {
|
||||
return { name: name, parser: parser};
|
||||
}
|
||||
const passThroughParser = parser;
|
||||
|
||||
const parserMap = new Map();
|
||||
parserMap.set(TYPE, passThroughParser("type"));
|
||||
parserMap.set(SPEC_VERSION, passThroughParser("specversion"));
|
||||
parserMap.set(SOURCE, passThroughParser("source"));
|
||||
parserMap.set(ID, passThroughParser("id"));
|
||||
parserMap.set(TIME, parser("time", (v) => new Date(Date.parse(v))));
|
||||
parserMap.set(SCHEMA_URL, passThroughParser("schemaurl"));
|
||||
parserMap.set(CONTENT_ENCODING, passThroughParser("dataContentEncoding"));
|
||||
parserMap.set(CONTENT_TYPE, passThroughParser("dataContentType"));
|
||||
parserMap.set(SUBJECT, passThroughParser("subject"));
|
||||
parserMap.set(DATA, passThroughParser("data"));
|
||||
|
||||
class Receiver {
|
||||
constructor() {
|
||||
this.parserByMime = parserByMime;
|
||||
this.parserMap = parserMap;
|
||||
this.allowedContentTypes = allowedContentTypes;
|
||||
this.Spec = Spec;
|
||||
this.spec = new Spec();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Receiver;
|
|
@ -1,265 +0,0 @@
|
|||
const { v4: uuidv4 } = require("uuid");
|
||||
const Ajv = require("ajv");
|
||||
const ValidationError = require("../validation/validation_error.js");
|
||||
|
||||
const {
|
||||
isBase64,
|
||||
clone,
|
||||
asData
|
||||
} = require("../validation/fun.js");
|
||||
|
||||
const RESERVED_ATTRIBUTES = {
|
||||
type: "type",
|
||||
specversion: "specversion",
|
||||
source: "source",
|
||||
id: "id",
|
||||
time: "time",
|
||||
schemaurl: "schemaurl",
|
||||
datacontentencoding: "datacontentencoding",
|
||||
datacontenttype: "datacontenttype",
|
||||
subject: "subject",
|
||||
data: "data"
|
||||
};
|
||||
|
||||
const SUPPORTED_CONTENT_ENCODING = {};
|
||||
SUPPORTED_CONTENT_ENCODING.base64 = {
|
||||
check: (data) => isBase64(data)
|
||||
};
|
||||
|
||||
const schema = {
|
||||
$ref: "#/definitions/event",
|
||||
definitions: {
|
||||
specversion: {
|
||||
const: "0.3"
|
||||
},
|
||||
datacontenttype: {
|
||||
type: "string"
|
||||
},
|
||||
data: {
|
||||
type: [
|
||||
"object",
|
||||
"string"
|
||||
]
|
||||
},
|
||||
event: {
|
||||
properties: {
|
||||
specversion: {
|
||||
$ref: "#/definitions/specversion"
|
||||
},
|
||||
datacontenttype: {
|
||||
$ref: "#/definitions/datacontenttype"
|
||||
},
|
||||
data: {
|
||||
$ref: "#/definitions/data"
|
||||
},
|
||||
id: {
|
||||
$ref: "#/definitions/id"
|
||||
},
|
||||
time: {
|
||||
$ref: "#/definitions/time"
|
||||
},
|
||||
schemaurl: {
|
||||
$ref: "#/definitions/schemaurl"
|
||||
},
|
||||
subject: {
|
||||
$ref: "#/definitions/subject"
|
||||
},
|
||||
type: {
|
||||
$ref: "#/definitions/type"
|
||||
},
|
||||
extensions: {
|
||||
$ref: "#/definitions/extensions"
|
||||
},
|
||||
source: {
|
||||
$ref: "#/definitions/source"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"specversion",
|
||||
"id",
|
||||
"type",
|
||||
"source"
|
||||
],
|
||||
type: "object"
|
||||
},
|
||||
id: {
|
||||
type: "string",
|
||||
minLength: 1
|
||||
},
|
||||
time: {
|
||||
format: "date-time",
|
||||
type: "string"
|
||||
},
|
||||
schemaurl: {
|
||||
type: "string",
|
||||
format: "uri-reference"
|
||||
},
|
||||
subject: {
|
||||
type: "string",
|
||||
minLength: 1
|
||||
},
|
||||
type: {
|
||||
type: "string",
|
||||
minLength: 1
|
||||
},
|
||||
extensions: {
|
||||
type: "object"
|
||||
},
|
||||
source: {
|
||||
format: "uri-reference",
|
||||
type: "string"
|
||||
}
|
||||
},
|
||||
type: "object"
|
||||
};
|
||||
|
||||
const ajv = new Ajv({
|
||||
extendRefs: true
|
||||
});
|
||||
|
||||
const isValidAgainstSchema = ajv.compile(schema);
|
||||
|
||||
/** @typedef {import("../../../cloudevent")} CloudEvent */
|
||||
|
||||
/**
|
||||
* Decorates a CloudEvent with the 0.3 specification getters and setters
|
||||
* @ignore
|
||||
*/
|
||||
class Spec03 {
|
||||
constructor() {
|
||||
this.payload = {
|
||||
specversion: schema.definitions.specversion.const,
|
||||
id: uuidv4(),
|
||||
time: new Date().toISOString()
|
||||
};
|
||||
}
|
||||
|
||||
check() {
|
||||
const toCheck = this.payload;
|
||||
|
||||
if (!isValidAgainstSchema(toCheck)) {
|
||||
throw new ValidationError("invalid payload", isValidAgainstSchema.errors);
|
||||
}
|
||||
|
||||
Array.of(toCheck)
|
||||
.filter((tc) => tc.datacontentencoding)
|
||||
.map((tc) => tc.datacontentencoding.toLocaleLowerCase("en-US"))
|
||||
.filter((dce) => !Object.keys(SUPPORTED_CONTENT_ENCODING).includes(dce))
|
||||
.forEach((dce) => {
|
||||
throw new ValidationError("invalid payload", [`Unsupported content encoding: ${dce}`]);
|
||||
});
|
||||
|
||||
Array.of(toCheck)
|
||||
.filter((tc) => tc.datacontentencoding)
|
||||
.filter((tc) => (typeof tc.data) === "string")
|
||||
.map((tc) => {
|
||||
const newtc = clone(tc);
|
||||
newtc.datacontentencoding =
|
||||
newtc.datacontentencoding.toLocaleLowerCase("en-US");
|
||||
|
||||
return newtc;
|
||||
})
|
||||
.filter((tc) => Object.keys(SUPPORTED_CONTENT_ENCODING)
|
||||
.includes(tc.datacontentencoding))
|
||||
.filter((tc) => !SUPPORTED_CONTENT_ENCODING[tc.datacontentencoding]
|
||||
.check(tc.data))
|
||||
.forEach((tc) => {
|
||||
throw new ValidationError("invalid payload", [`Invalid content encoding of data: ${tc.data}`]);
|
||||
});
|
||||
}
|
||||
|
||||
set id(id) {
|
||||
this.payload.id = id;
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this.payload.id;
|
||||
}
|
||||
|
||||
set source(source) {
|
||||
this.payload.source = source;
|
||||
}
|
||||
|
||||
get source() {
|
||||
return this.payload.source;
|
||||
}
|
||||
|
||||
get specversion() {
|
||||
return this.payload.specversion;
|
||||
}
|
||||
|
||||
set type(type) {
|
||||
this.payload.type = type;
|
||||
}
|
||||
|
||||
get type() {
|
||||
return this.payload.type;
|
||||
}
|
||||
|
||||
set dataContentType(datacontenttype) {
|
||||
this.payload.datacontenttype = datacontenttype;
|
||||
}
|
||||
|
||||
get dataContentType() {
|
||||
return this.payload.datacontenttype;
|
||||
}
|
||||
|
||||
set schemaURL(schema) {
|
||||
this.payload.schemaURL = schema;
|
||||
}
|
||||
|
||||
get schemaURL() {
|
||||
return this.payload.schemaURL;
|
||||
}
|
||||
|
||||
set subject(subject) {
|
||||
this.payload.subject = subject;
|
||||
}
|
||||
|
||||
get subject() {
|
||||
return this.payload.subject;
|
||||
}
|
||||
|
||||
set time(time) {
|
||||
this.payload.time = time;
|
||||
}
|
||||
|
||||
get time() {
|
||||
return this.payload.time;
|
||||
}
|
||||
|
||||
set data(data) {
|
||||
this.payload.data = data;
|
||||
}
|
||||
|
||||
get data() {
|
||||
const dct = this.payload.datacontenttype;
|
||||
const dce = this.payload.datacontentencoding;
|
||||
|
||||
if (dct && !dce) {
|
||||
this.payload.data = asData(this.payload.data, dct);
|
||||
}
|
||||
|
||||
return this.payload.data;
|
||||
}
|
||||
|
||||
set dataContentEncoding(encoding) {
|
||||
this.payload.datacontentencoding = encoding;
|
||||
}
|
||||
|
||||
get dataContentEncoding() {
|
||||
return this.payload.datacontentencoding;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Spec03.prototype.addExtension = function(key, value) {
|
||||
if (!Object.prototype.hasOwnProperty.call(RESERVED_ATTRIBUTES, key)) {
|
||||
this.payload[key] = value;
|
||||
} else {
|
||||
throw new ValidationError(`Reserved attribute name: '${key}'`);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
module.exports = Spec03;
|
|
@ -1,33 +0,0 @@
|
|||
const {
|
||||
HEADER_CONTENT_TYPE,
|
||||
BINARY_HEADERS_1 : {
|
||||
SUBJECT,
|
||||
TYPE,
|
||||
SPEC_VERSION,
|
||||
SOURCE,
|
||||
ID,
|
||||
TIME,
|
||||
DATA_SCHEMA
|
||||
}
|
||||
} = require("../constants");
|
||||
|
||||
function parser(header, parser = (v) => v) {
|
||||
return { headerName: header, parse: parser };
|
||||
}
|
||||
const passThroughParser = parser;
|
||||
|
||||
/**
|
||||
* A utility Map used to retrieve the header names for a CloudEvent
|
||||
* using the CloudEvent getter function.
|
||||
*/
|
||||
const headerMap = new Map();
|
||||
headerMap.set("dataContentType", passThroughParser(HEADER_CONTENT_TYPE));
|
||||
headerMap.set("subject", passThroughParser(SUBJECT));
|
||||
headerMap.set("type", passThroughParser(TYPE));
|
||||
headerMap.set("specversion", passThroughParser(SPEC_VERSION));
|
||||
headerMap.set("source", passThroughParser(SOURCE));
|
||||
headerMap.set("id", passThroughParser(ID));
|
||||
headerMap.set("time", passThroughParser(TIME));
|
||||
headerMap.set("dataSchema", passThroughParser(DATA_SCHEMA));
|
||||
|
||||
module.exports = headerMap;
|
|
@ -1,9 +0,0 @@
|
|||
const Spec = require("./spec_1.js");
|
||||
const BinaryEmitter = require("./emitter_binary_1.js");
|
||||
const StructuredEmitter = require("./receiver_structured_1.js");
|
||||
|
||||
module.exports = {
|
||||
Spec,
|
||||
BinaryEmitter,
|
||||
StructuredEmitter
|
||||
};
|
|
@ -1,62 +0,0 @@
|
|||
const {
|
||||
SPEC_V1,
|
||||
MIME_JSON,
|
||||
MIME_OCTET_STREAM,
|
||||
ENCODING_BASE64,
|
||||
BINARY_HEADERS_1,
|
||||
HEADER_CONTENT_TYPE
|
||||
} = require("../constants");
|
||||
|
||||
const Spec = require("./spec_1.js");
|
||||
const JSONParser = require("../../../formats/json/parser.js");
|
||||
const Base64Parser = require("../../../formats/base64.js");
|
||||
|
||||
const parserByType = {
|
||||
[MIME_JSON] : new JSONParser(),
|
||||
[MIME_OCTET_STREAM] : { parse(payload) { return payload; } }
|
||||
};
|
||||
|
||||
const parsersByEncoding = {
|
||||
null: parserByType,
|
||||
undefined: parserByType,
|
||||
[ENCODING_BASE64]: {
|
||||
[MIME_JSON]: new JSONParser(new Base64Parser()),
|
||||
[MIME_OCTET_STREAM]: {
|
||||
parse(payload) { return payload; }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const allowedContentTypes = [MIME_JSON, MIME_OCTET_STREAM];
|
||||
|
||||
const requiredHeaders = [BINARY_HEADERS_1.TYPE, BINARY_HEADERS_1.SPEC_VERSION,
|
||||
BINARY_HEADERS_1.SOURCE, BINARY_HEADERS_1.ID];
|
||||
|
||||
const passThroughParser = (v) => v;
|
||||
|
||||
const setterByHeader = {
|
||||
[BINARY_HEADERS_1.TYPE] : { name: "type", parser: passThroughParser },
|
||||
[BINARY_HEADERS_1.SPEC_VERSION] : { name: "specversion", parser: () => "1.0" },
|
||||
[BINARY_HEADERS_1.SOURCE] : { name: "source", parser: passThroughParser },
|
||||
[BINARY_HEADERS_1.ID] : { name: "id", parser: passThroughParser },
|
||||
[BINARY_HEADERS_1.TIME] : { name: "time", parser: (v) => new Date(Date.parse(v)) },
|
||||
[BINARY_HEADERS_1.DATA_SCHEMA] : { name: "dataSchema", parser: passThroughParser },
|
||||
[HEADER_CONTENT_TYPE] : { name: "dataContentType", parser: passThroughParser },
|
||||
[BINARY_HEADERS_1.SUBJECT] : { name: "subject", parser: passThroughParser }
|
||||
};
|
||||
|
||||
class Receiver {
|
||||
constructor() {
|
||||
this.parserByType = parserByType;
|
||||
this.parsersByEncoding = parsersByEncoding;
|
||||
this.allowedContentTypes = allowedContentTypes;
|
||||
this.requiredHeaders = requiredHeaders;
|
||||
this.setterByHeader = setterByHeader;
|
||||
this.specversion = SPEC_V1;
|
||||
this.extensionsPrefix = BINARY_HEADERS_1.EXTENSIONS_PREFIX;
|
||||
this.Spec = Spec;
|
||||
this.spec = new Spec();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Receiver;
|
|
@ -1,57 +0,0 @@
|
|||
const {
|
||||
MIME_CE_JSON,
|
||||
STRUCTURED_ATTRS_1 : {
|
||||
TYPE,
|
||||
SPEC_VERSION,
|
||||
SOURCE,
|
||||
ID,
|
||||
TIME,
|
||||
DATA_SCHEMA,
|
||||
CONTENT_TYPE,
|
||||
SUBJECT,
|
||||
DATA,
|
||||
DATA_BASE64,
|
||||
MIME_JSON
|
||||
}
|
||||
} = require("../constants");
|
||||
|
||||
const Spec = require("./spec_1.js");
|
||||
const JSONParser = require("../../../formats/json/parser.js");
|
||||
|
||||
const jsonParser = new JSONParser();
|
||||
|
||||
const parserByMime = {
|
||||
[MIME_JSON]: jsonParser,
|
||||
[MIME_CE_JSON]: jsonParser
|
||||
};
|
||||
|
||||
const allowedContentTypes = [ MIME_CE_JSON ];
|
||||
|
||||
function parser(name, parser = (v) => v) {
|
||||
return { name: name, parser: parser};
|
||||
}
|
||||
const passThroughParser = parser;
|
||||
|
||||
const parserMap = new Map();
|
||||
parserMap.set(TYPE, passThroughParser("type"));
|
||||
parserMap.set(SPEC_VERSION, passThroughParser("specversion"));
|
||||
parserMap.set(SOURCE, passThroughParser("source"));
|
||||
parserMap.set(ID, passThroughParser("id"));
|
||||
parserMap.set(TIME, parser("time", (v) => new Date(Date.parse(v))));
|
||||
parserMap.set(DATA_SCHEMA, passThroughParser("dataSchema"));
|
||||
parserMap.set(CONTENT_TYPE, passThroughParser("dataContentType"));
|
||||
parserMap.set(SUBJECT, passThroughParser("subject"));
|
||||
parserMap.set(DATA, passThroughParser("data"));
|
||||
parserMap.set(DATA_BASE64, passThroughParser("data"));
|
||||
|
||||
class Receiver {
|
||||
constructor() {
|
||||
this.parserByMime = parserByMime;
|
||||
this.parserMap = parserMap;
|
||||
this.allowedContentTypes = allowedContentTypes;
|
||||
this.Spec = Spec;
|
||||
this.spec = new Spec();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Receiver;
|
|
@ -1,227 +0,0 @@
|
|||
const { v4: uuidv4 } = require("uuid");
|
||||
const Ajv = require("ajv");
|
||||
const ValidationError = require("../validation/validation_error.js");
|
||||
|
||||
const {
|
||||
asData,
|
||||
isBoolean,
|
||||
isInteger,
|
||||
isString,
|
||||
isDate,
|
||||
isBinary
|
||||
} = require("../validation/fun.js");
|
||||
|
||||
const isValidType = (v) =>
|
||||
(isBoolean(v) || isInteger(v) || isString(v) || isDate(v) || isBinary(v));
|
||||
|
||||
const RESERVED_ATTRIBUTES = {
|
||||
type: "type",
|
||||
specversion: "specversion",
|
||||
source: "source",
|
||||
id: "id",
|
||||
time: "time",
|
||||
dataschema: "dataschema",
|
||||
datacontenttype: "datacontenttype",
|
||||
subject: "subject",
|
||||
data: "data",
|
||||
data_base64: "data_base64"
|
||||
};
|
||||
|
||||
const schema = {
|
||||
$ref: "#/definitions/event",
|
||||
definitions: {
|
||||
specversion: {
|
||||
type: "string",
|
||||
minLength: 1,
|
||||
const: "1.0"
|
||||
},
|
||||
datacontenttype: {
|
||||
type: "string"
|
||||
},
|
||||
data: {
|
||||
type: ["object", "string"]
|
||||
},
|
||||
data_base64: {
|
||||
type: "string"
|
||||
},
|
||||
event: {
|
||||
properties: {
|
||||
specversion: {
|
||||
$ref: "#/definitions/specversion"
|
||||
},
|
||||
datacontenttype: {
|
||||
$ref: "#/definitions/datacontenttype"
|
||||
},
|
||||
data: {
|
||||
$ref: "#/definitions/data"
|
||||
},
|
||||
data_base64: {
|
||||
$ref: "#/definitions/data_base64"
|
||||
},
|
||||
id: {
|
||||
$ref: "#/definitions/id"
|
||||
},
|
||||
time: {
|
||||
$ref: "#/definitions/time"
|
||||
},
|
||||
dataschema: {
|
||||
$ref: "#/definitions/dataschema"
|
||||
},
|
||||
subject: {
|
||||
$ref: "#/definitions/subject"
|
||||
},
|
||||
type: {
|
||||
$ref: "#/definitions/type"
|
||||
},
|
||||
source: {
|
||||
$ref: "#/definitions/source"
|
||||
}
|
||||
},
|
||||
required: ["specversion", "id", "type", "source"],
|
||||
type: "object"
|
||||
},
|
||||
id: {
|
||||
type: "string",
|
||||
minLength: 1
|
||||
},
|
||||
time: {
|
||||
format: "date-time",
|
||||
type: "string"
|
||||
},
|
||||
dataschema: {
|
||||
type: "string",
|
||||
format: "uri"
|
||||
},
|
||||
subject: {
|
||||
type: "string",
|
||||
minLength: 1
|
||||
},
|
||||
type: {
|
||||
type: "string",
|
||||
minLength: 1
|
||||
},
|
||||
source: {
|
||||
format: "uri-reference",
|
||||
type: "string"
|
||||
}
|
||||
},
|
||||
type: "object"
|
||||
};
|
||||
|
||||
const ajv = new Ajv({
|
||||
extendRefs: true
|
||||
});
|
||||
|
||||
const isValidAgainstSchema = ajv.compile(schema);
|
||||
|
||||
/** @typedef {import("../../../cloudevent")} CloudEvent */
|
||||
|
||||
/**
|
||||
* Decorates a CloudEvent with the 1.0 specification getters and setters
|
||||
* @ignore
|
||||
*/
|
||||
class Spec1 {
|
||||
constructor() {
|
||||
this.payload = {
|
||||
specversion: schema.definitions.specversion.const,
|
||||
id: uuidv4(),
|
||||
time: new Date().toISOString()
|
||||
};
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
check() {
|
||||
if (!isValidAgainstSchema(this.payload)) {
|
||||
throw new ValidationError("invalid payload", isValidAgainstSchema.errors);
|
||||
}
|
||||
}
|
||||
|
||||
set id(id) {
|
||||
this.payload.id = id;
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this.payload.id;
|
||||
}
|
||||
|
||||
set source(source) {
|
||||
this.payload.source = source;
|
||||
}
|
||||
|
||||
get source() {
|
||||
return this.payload.source;
|
||||
}
|
||||
|
||||
get specversion() {
|
||||
return this.payload.specversion;
|
||||
}
|
||||
|
||||
set type(type) {
|
||||
this.payload.type = type;
|
||||
}
|
||||
|
||||
get type() {
|
||||
return this.payload.type;
|
||||
}
|
||||
|
||||
set dataContentType(datacontenttype) {
|
||||
this.payload.datacontenttype = datacontenttype;
|
||||
}
|
||||
|
||||
get dataContentType() {
|
||||
return this.payload.datacontenttype;
|
||||
}
|
||||
|
||||
set dataSchema(schema) {
|
||||
this.payload.dataSchema = schema;
|
||||
}
|
||||
|
||||
get dataSchema() {
|
||||
return this.payload.dataSchema;
|
||||
}
|
||||
|
||||
set subject(subject) {
|
||||
this.payload.subject = subject;
|
||||
}
|
||||
|
||||
get subject() {
|
||||
return this.payload.subject;
|
||||
}
|
||||
|
||||
set time(time) {
|
||||
this.payload.time = time;
|
||||
}
|
||||
|
||||
get time() {
|
||||
return this.payload.time;
|
||||
}
|
||||
|
||||
set data(data) {
|
||||
this.payload.data = data;
|
||||
}
|
||||
|
||||
get data() {
|
||||
const dct = this.payload.datacontenttype;
|
||||
|
||||
if (dct) {
|
||||
this.payload.data = asData(this.payload.data, dct);
|
||||
}
|
||||
|
||||
return this.payload.data;
|
||||
}
|
||||
|
||||
addExtension(key, value) {
|
||||
if (!Object.prototype.hasOwnProperty.call(RESERVED_ATTRIBUTES, key)) {
|
||||
if (isValidType(value)) {
|
||||
this.payload[key] = value;
|
||||
} else {
|
||||
throw new ValidationError("Invalid type of extension value");
|
||||
}
|
||||
} else {
|
||||
throw new ValidationError(`Reserved attribute name: '${key}'`);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Spec1;
|
|
@ -1,91 +0,0 @@
|
|||
const { CloudEvent } = require("../../../cloudevent");
|
||||
|
||||
const {
|
||||
sanityAndClone,
|
||||
validateArgs
|
||||
} = require("./commons.js");
|
||||
const ValidationError = require("./validation_error.js");
|
||||
const {
|
||||
HEADER_CONTENT_TYPE,
|
||||
MIME_JSON,
|
||||
DEFAULT_SPEC_VERSION_HEADER
|
||||
} = require("../constants");
|
||||
|
||||
const {
|
||||
isString,
|
||||
isObject,
|
||||
isBase64
|
||||
} = require("./fun.js");
|
||||
|
||||
function check(payload, headers, receiver) {
|
||||
// Validation Level 0
|
||||
validateArgs(payload, headers);
|
||||
|
||||
// The receiver determines the specification version
|
||||
if (!isObject(receiver)) throw new SyntaxError("no receiver");
|
||||
|
||||
// Clone and low case all headers names
|
||||
const sanityHeaders = sanityAndClone(headers);
|
||||
|
||||
// Validation Level 1 - if content-type exists, be sure it's
|
||||
// an allowed type
|
||||
const contentTypeHeader = sanityHeaders[HEADER_CONTENT_TYPE];
|
||||
const noContentType = !receiver.allowedContentTypes.includes(contentTypeHeader);
|
||||
if (contentTypeHeader && noContentType) {
|
||||
throw new ValidationError("invalid content type", [sanityHeaders[HEADER_CONTENT_TYPE]]);
|
||||
}
|
||||
|
||||
receiver.requiredHeaders
|
||||
.filter((required) => !sanityHeaders[required])
|
||||
.forEach((required) => {
|
||||
throw new ValidationError(`header '${required}' not found`);
|
||||
});
|
||||
|
||||
if (sanityHeaders[DEFAULT_SPEC_VERSION_HEADER] !== receiver.specversion) {
|
||||
throw new ValidationError("invalid spec version", [sanityHeaders[DEFAULT_SPEC_VERSION_HEADER]]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function parse(payload, headers, receiver) {
|
||||
payload = isString(payload) && isBase64(payload)
|
||||
? Buffer.from(payload, "base64").toString()
|
||||
: payload;
|
||||
|
||||
check(payload, headers, receiver);
|
||||
|
||||
// Clone and low case all headers names
|
||||
const sanityHeaders = sanityAndClone(headers);
|
||||
if (!sanityHeaders[HEADER_CONTENT_TYPE]) {
|
||||
sanityHeaders[HEADER_CONTENT_TYPE] = MIME_JSON;
|
||||
}
|
||||
|
||||
const eventObj = {};
|
||||
const setterByHeader = receiver.setterByHeader;
|
||||
Array.from(Object.keys(setterByHeader))
|
||||
.filter((header) => sanityHeaders[header])
|
||||
.forEach((header) => {
|
||||
eventObj[setterByHeader[header].name] = setterByHeader[header].parser(sanityHeaders[header]);
|
||||
delete sanityHeaders[header];
|
||||
});
|
||||
|
||||
// Parses the payload
|
||||
const parser = receiver.parsersByEncoding[eventObj.dataContentEncoding][eventObj.dataContentType];
|
||||
const parsedPayload = parser.parse(payload);
|
||||
const cloudevent = new CloudEvent({ source: undefined, type: undefined, ...eventObj, data: parsedPayload });
|
||||
|
||||
// Every unprocessed header can be an extension
|
||||
Array.from(Object.keys(sanityHeaders))
|
||||
.filter((value) => value.startsWith(receiver.extensionsPrefix))
|
||||
.map((extension) => extension.substring(receiver.extensionsPrefix.length))
|
||||
.forEach((extension) => cloudevent.addExtension(extension,
|
||||
sanityHeaders[receiver.extensionsPrefix + extension]));
|
||||
|
||||
// Validates the event
|
||||
cloudevent.spec.check();
|
||||
return cloudevent;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
check, parse
|
||||
};
|
|
@ -1,51 +0,0 @@
|
|||
const ValidationError = require("./validation_error.js");
|
||||
const Constants = require("../constants");
|
||||
const {
|
||||
isDefinedOrThrow,
|
||||
isStringOrObjectOrThrow
|
||||
} = require("./fun.js");
|
||||
|
||||
|
||||
// Specific sanity for content-type header
|
||||
function sanityContentType(contentType) {
|
||||
if (contentType) {
|
||||
return Array.of(contentType)
|
||||
.map((c) => c.split(";"))
|
||||
.map((c) => c.shift())
|
||||
.shift();
|
||||
}
|
||||
|
||||
return contentType;
|
||||
}
|
||||
|
||||
function sanityAndClone(headers) {
|
||||
const sanityHeaders = {};
|
||||
|
||||
Array.from(Object.keys(headers))
|
||||
.filter((header) => Object.hasOwnProperty.call(headers, header))
|
||||
.forEach((header) => {
|
||||
sanityHeaders[header.toLowerCase()] = headers[header];
|
||||
});
|
||||
|
||||
sanityHeaders[Constants.HEADER_CONTENT_TYPE] =
|
||||
sanityContentType(sanityHeaders[Constants.HEADER_CONTENT_TYPE]);
|
||||
|
||||
return sanityHeaders;
|
||||
}
|
||||
|
||||
function validateArgs(payload, attributes) {
|
||||
Array.of(payload)
|
||||
.filter((p) => isDefinedOrThrow(p, new ValidationError("payload is null or undefined")))
|
||||
.filter((p) => isStringOrObjectOrThrow(p, new ValidationError("payload must be an object or a string")))
|
||||
.shift();
|
||||
|
||||
Array.of(attributes)
|
||||
.filter((a) => isDefinedOrThrow(a, new ValidationError("attributes is null or undefined")))
|
||||
.shift();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
sanityAndClone,
|
||||
sanityContentType,
|
||||
validateArgs
|
||||
};
|
|
@ -1,88 +0,0 @@
|
|||
// Functional approach
|
||||
const isString = (v) => (typeof v) === "string";
|
||||
const isObject = (v) => (typeof v) === "object";
|
||||
const isDefined = (v) => v && (typeof v) !== "undefined";
|
||||
|
||||
const isBoolean = (v) => (typeof v) === "boolean";
|
||||
const isInteger = (v) => Number.isInteger(v);
|
||||
const isDate = (v) => (v instanceof Date);
|
||||
const isBinary = (v) => (v instanceof Uint32Array);
|
||||
|
||||
const isStringOrThrow = (v, t) =>
|
||||
(isString(v)
|
||||
? true
|
||||
: (() => { throw t; })());
|
||||
|
||||
const isDefinedOrThrow = (v, t) =>
|
||||
(isDefined(v)
|
||||
? () => true
|
||||
: (() => { throw t; })());
|
||||
|
||||
const isStringOrObjectOrThrow = (v, t) =>
|
||||
(isString(v)
|
||||
? true
|
||||
: isObject(v)
|
||||
? true
|
||||
: (() => { throw t; })());
|
||||
|
||||
const equalsOrThrow = (v1, v2, t) =>
|
||||
(v1 === v2
|
||||
? true
|
||||
: (() => { throw t; })());
|
||||
|
||||
const isBase64 = (value) =>
|
||||
Buffer.from(value, "base64").toString("base64") === value;
|
||||
|
||||
const isBuffer = (value) =>
|
||||
value instanceof Buffer;
|
||||
|
||||
const asBuffer = (value) =>
|
||||
isBinary(value)
|
||||
? Buffer.from(value)
|
||||
: isBuffer(value)
|
||||
? value
|
||||
: (() => { throw new TypeError("is not buffer or a valid binary"); })();
|
||||
|
||||
const asBase64 = (value) =>
|
||||
asBuffer(value).toString("base64");
|
||||
|
||||
const clone = (o) =>
|
||||
JSON.parse(JSON.stringify(o));
|
||||
|
||||
const isJsonContentType = (contentType) =>
|
||||
contentType && contentType.match(/(json)/i);
|
||||
|
||||
const asData = (data, contentType) => {
|
||||
// pattern matching alike
|
||||
const maybeJson = isString(data) &&
|
||||
!isBase64(data) &&
|
||||
isJsonContentType(contentType)
|
||||
? JSON.parse(data)
|
||||
: data;
|
||||
|
||||
return isBinary(maybeJson)
|
||||
? asBase64(maybeJson)
|
||||
: maybeJson;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
isString,
|
||||
isStringOrThrow,
|
||||
isObject,
|
||||
isDefined,
|
||||
|
||||
isBoolean,
|
||||
isInteger,
|
||||
isDate,
|
||||
isBinary,
|
||||
|
||||
isDefinedOrThrow,
|
||||
isStringOrObjectOrThrow,
|
||||
|
||||
equalsOrThrow,
|
||||
isBase64,
|
||||
clone,
|
||||
|
||||
asData,
|
||||
asBase64
|
||||
};
|
|
@ -1,52 +0,0 @@
|
|||
const { CloudEvent } = require("../../../cloudevent");
|
||||
const ValidationError = require("./validation_error.js");
|
||||
const {
|
||||
sanityAndClone,
|
||||
validateArgs
|
||||
} = require("./commons.js");
|
||||
const {
|
||||
HEADER_CONTENT_TYPE
|
||||
} = require("../constants");
|
||||
|
||||
function check(payload, headers, receiver) {
|
||||
validateArgs(payload, headers);
|
||||
|
||||
const sanityHeaders = sanityAndClone(headers);
|
||||
|
||||
// Validation Level 1
|
||||
if (!receiver.allowedContentTypes.includes(sanityHeaders[HEADER_CONTENT_TYPE])) {
|
||||
throw new ValidationError("invalid content type", [sanityHeaders[HEADER_CONTENT_TYPE]]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function parse(payload, headers, receiver) {
|
||||
check(payload, headers, receiver);
|
||||
|
||||
const sanityHeaders = sanityAndClone(headers);
|
||||
const contentType = sanityHeaders[HEADER_CONTENT_TYPE];
|
||||
const parser = receiver.parserByMime[contentType];
|
||||
const incoming = { ...parser.parse(payload) };
|
||||
const event = {
|
||||
type: undefined,
|
||||
source: undefined
|
||||
};
|
||||
|
||||
receiver.parserMap.forEach((value, key) => {
|
||||
if (incoming[key]) {
|
||||
event[value.name] = value.parser(incoming[key]);
|
||||
delete incoming[key];
|
||||
}
|
||||
});
|
||||
|
||||
const cloudevent = new CloudEvent(event);
|
||||
|
||||
// Every unprocessed attribute should be an extension
|
||||
Array.from(Object.keys(incoming)).forEach((extension) =>
|
||||
cloudevent.addExtension(extension, incoming[extension]));
|
||||
|
||||
cloudevent.spec.check();
|
||||
return cloudevent;
|
||||
}
|
||||
|
||||
module.exports = { parse, check };
|
|
@ -1,23 +0,0 @@
|
|||
/**
|
||||
* @ignore
|
||||
* @typedef {import("ajv").ErrorObject} ErrorObject
|
||||
* */
|
||||
|
||||
/**
|
||||
* A Error class that will be thrown when a CloudEvent
|
||||
* cannot be properly validated against a specification.
|
||||
*/
|
||||
class ValidationError extends TypeError {
|
||||
/**
|
||||
* Constructs a new {ValidationError} with the message
|
||||
* and array of additional errors.
|
||||
* @param {string} message the error message
|
||||
* @param {string[]|ErrorObject[]} [errors] any additional errors related to validation
|
||||
*/
|
||||
constructor(message, errors) {
|
||||
super(message);
|
||||
this.errors = errors ? errors : [];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ValidationError;
|
|
@ -1,314 +0,0 @@
|
|||
import { CloudEventV1, CloudEventV1Attributes } from "./v1";
|
||||
import { CloudEventV03, CloudEventV03Attributes } from "./v03";
|
||||
|
||||
import Spec1 from "./bindings/http/v1/spec_1.js";
|
||||
import Spec03 from "./bindings/http/v03/spec_0_3.js";
|
||||
import Formatter from "./formats/json/formatter.js";
|
||||
import { isBinary } from "./bindings/http/validation/fun.js";
|
||||
import Extensions from "./extensions";
|
||||
|
||||
const { SPEC_V1, SPEC_V03 } = require("./bindings/http/constants");
|
||||
|
||||
export type CE = CloudEventV1 | CloudEventV1Attributes | CloudEventV03 | CloudEventV03Attributes
|
||||
|
||||
/**
|
||||
* A CloudEvent describes event data in common formats to provide
|
||||
* interoperability across services, platforms and systems.
|
||||
* @see https://github.com/cloudevents/spec/blob/v1.0/spec.md
|
||||
*/
|
||||
export class CloudEvent {
|
||||
spec: any;
|
||||
formatter: any;
|
||||
extensions: Extensions;
|
||||
|
||||
/**
|
||||
* Creates a new CloudEvent instance
|
||||
* @param {object} event CloudEvent properties as a simple object
|
||||
* @param {string} event.source Identifies the context in which an event happened as a URI reference
|
||||
* @param {string} event.type Describes the type of event related to the originating occurrence
|
||||
* @param {string} [event.id] A unique ID for this event - if not supplied, will be autogenerated
|
||||
* @param {string} [event.time] A timestamp for this event. May also be provided as a Date
|
||||
* @param {string} [event.subject] Describes the subject of the event in the context of the event producer
|
||||
* @param {string} [event.dataContentType] The mime content type for the event data
|
||||
* @param {string} [event.dataSchema] The URI of the schema that the event data adheres to (v1.0 events)
|
||||
* @param {string} [event.schemaURL] The URI of the schema that the event data adheres to (v0.3 events)
|
||||
* @param {string} [event.dataContentEncoding] The content encoding for the event data (v0.3 events)
|
||||
* @param {string} [event.specversion] The CloudEvent specification version for this event - default: 1.0
|
||||
* @param {object} [event.extensions] The CloudEvent extensions for this event
|
||||
* @param {*} [event.data] The event payload
|
||||
*/
|
||||
constructor(event: CE) {
|
||||
if (!event || !event.type || !event.source) {
|
||||
throw new TypeError("event type and source are required");
|
||||
}
|
||||
|
||||
switch (event.specversion) {
|
||||
case SPEC_V1:
|
||||
this.spec = new Spec1();
|
||||
break;
|
||||
case SPEC_V03:
|
||||
this.spec = new Spec03();
|
||||
break;
|
||||
case undefined:
|
||||
this.spec = new Spec1();
|
||||
break;
|
||||
default:
|
||||
throw new TypeError(`unknown specification version ${event.specversion}`);
|
||||
}
|
||||
this.source = event.source;
|
||||
this.type = event.type;
|
||||
this.dataContentType = event.dataContentType;
|
||||
this.data = event.data;
|
||||
this.subject = event.subject;
|
||||
|
||||
if (event.dataSchema) {
|
||||
this.dataSchema = event.dataSchema;
|
||||
}
|
||||
|
||||
// TODO: Deprecated in 1.0
|
||||
if (event.dataContentEncoding) {
|
||||
this.dataContentEncoding = event.dataContentEncoding;
|
||||
}
|
||||
|
||||
// TODO: Deprecated in 1.0
|
||||
if (event.schemaURL) {
|
||||
this.schemaURL = event.schemaURL;
|
||||
}
|
||||
|
||||
if (event.id) {
|
||||
this.id = event.id;
|
||||
}
|
||||
|
||||
if (event.time) {
|
||||
this.time = event.time;
|
||||
}
|
||||
this.formatter = new Formatter();
|
||||
|
||||
this.extensions = {};
|
||||
if (event.extensions) {
|
||||
for (const key in event.extensions) {
|
||||
this.addExtension(key, event.extensions[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the event id. Source + id must be unique for each distinct event.
|
||||
* @see https://github.com/cloudevents/spec/blob/master/spec.md#id
|
||||
* @type {string}
|
||||
*/
|
||||
get id() {
|
||||
return this.spec.id;
|
||||
}
|
||||
|
||||
set id(id) {
|
||||
this.spec.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the origination source of this event as a URI.
|
||||
* @type {string}
|
||||
* @see https://github.com/cloudevents/spec/blob/master/spec.md#source-1
|
||||
*/
|
||||
get source() {
|
||||
return this.spec.source;
|
||||
}
|
||||
|
||||
set source(source) {
|
||||
this.spec.source = source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the CloudEvent specification version
|
||||
* @type {string}
|
||||
* @see https://github.com/cloudevents/spec/blob/master/spec.md#specversion
|
||||
*/
|
||||
get specversion() {
|
||||
return this.spec.specversion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the event type
|
||||
* @type {string}
|
||||
* @see https://github.com/cloudevents/spec/blob/master/spec.md#type
|
||||
*/
|
||||
get type() {
|
||||
return this.spec.type;
|
||||
}
|
||||
|
||||
set type(type) {
|
||||
this.spec.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the content type of the data value for this event
|
||||
* @type {string}
|
||||
* @see https://github.com/cloudevents/spec/blob/master/spec.md#datacontenttype
|
||||
*/
|
||||
get dataContentType() {
|
||||
return this.spec.dataContentType;
|
||||
}
|
||||
|
||||
set dataContentType(contenttype) {
|
||||
this.spec.dataContentType = contenttype;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the event's data schema
|
||||
* @type {string}
|
||||
* @see https://github.com/cloudevents/spec/blob/v1.0/spec.md#dataschema
|
||||
*/
|
||||
get dataSchema() {
|
||||
if (this.spec instanceof Spec1) {
|
||||
return this.spec.dataSchema;
|
||||
}
|
||||
throw new TypeError("cannot get dataSchema from version 0.3 event");
|
||||
}
|
||||
|
||||
set dataSchema(dataschema) {
|
||||
if (this.spec instanceof Spec1) {
|
||||
this.spec.dataSchema = dataschema;
|
||||
} else {
|
||||
throw new TypeError("cannot set dataSchema on version 0.3 event");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the event's data content encoding
|
||||
* @type {string}
|
||||
* @see https://github.com/cloudevents/spec/blob/v0.3/spec.md#datacontentencoding
|
||||
*/
|
||||
get dataContentEncoding() {
|
||||
if (this.spec instanceof Spec03) {
|
||||
return this.spec.dataContentEncoding;
|
||||
}
|
||||
throw new TypeError("cannot get dataContentEncoding from version 1.0 event");
|
||||
}
|
||||
|
||||
set dataContentEncoding(dataContentEncoding) {
|
||||
if (this.spec instanceof Spec03) {
|
||||
this.spec.dataContentEncoding = dataContentEncoding;
|
||||
} else {
|
||||
throw new TypeError("cannot set dataContentEncoding on version 1.0 event");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the event subject
|
||||
* @type {string}
|
||||
* @see https://github.com/cloudevents/spec/blob/v1.0/spec.md#subject
|
||||
*/
|
||||
get subject() {
|
||||
return this.spec.subject;
|
||||
}
|
||||
|
||||
set subject(subject) {
|
||||
this.spec.subject = subject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the timestamp for this event as an ISO formatted date string
|
||||
* @type {string}
|
||||
* @see https://github.com/cloudevents/spec/blob/master/spec.md#time
|
||||
*/
|
||||
get time() {
|
||||
return this.spec.time;
|
||||
}
|
||||
|
||||
set time(time) {
|
||||
this.spec.time = new Date(time).toISOString();
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED: Gets or sets the schema URL for this event. Throws {TypeError}
|
||||
* if this is a version 1.0 event.
|
||||
* @type {string}
|
||||
* @see https://github.com/cloudevents/spec/blob/v0.3/spec.md#schemaurl
|
||||
*/
|
||||
get schemaURL() {
|
||||
if (this.spec instanceof Spec03) {
|
||||
return this.spec.schemaURL;
|
||||
}
|
||||
throw new TypeError("cannot get schemaURL from version 1.0 event");
|
||||
}
|
||||
|
||||
// TODO: Deprecated in 1.0
|
||||
set schemaURL(schemaurl) {
|
||||
if (schemaurl && (this.spec instanceof Spec03)) {
|
||||
this.spec.schemaURL = schemaurl;
|
||||
} else if (schemaurl) {
|
||||
throw new TypeError("cannot set schemaURL on version 1.0 event");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets or sets the data for this event
|
||||
* @see https://github.com/cloudevents/spec/blob/master/spec.md#event-data
|
||||
* @type {*}
|
||||
*/
|
||||
get data() {
|
||||
return this.spec.data;
|
||||
}
|
||||
|
||||
set data(data) {
|
||||
this.spec.data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the CloudEvent as JSON. Validates the event according
|
||||
* to the CloudEvent specification and throws an exception if
|
||||
* it's invalid.
|
||||
* @returns {JSON} the CloudEvent in JSON form
|
||||
* @throws {ValidationError} if this event cannot be validated against the specification
|
||||
*/
|
||||
format() {
|
||||
this.spec.check();
|
||||
const payload = {
|
||||
data: undefined,
|
||||
data_base64: undefined,
|
||||
...this.spec.payload
|
||||
};
|
||||
|
||||
// Handle when is binary, creating the data_base64
|
||||
if (isBinary(payload.data)) {
|
||||
// TODO: The call to this.spec.data formats the binary data
|
||||
// I think having a side effect like this is an anti-pattern.
|
||||
// FIXIT
|
||||
payload.data_base64 = this.spec.data;
|
||||
delete payload.data;
|
||||
} else {
|
||||
delete payload.data_base64;
|
||||
}
|
||||
return this.formatter.format(payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the CloudEvent as JSON. No specification validation is performed.
|
||||
* @returns {string} the CloudEvent as a JSON string
|
||||
*/
|
||||
toString() {
|
||||
return this.formatter.toString(this.spec.payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an extension attribute to this CloudEvent
|
||||
* @see https://github.com/cloudevents/spec/blob/master/spec.md#extension-context-attributes
|
||||
* @param {string} key the name of the extension attribute
|
||||
* @param {*} value the value of the extension attribute
|
||||
* @returns {void}
|
||||
*/
|
||||
addExtension(key: string, value: any): void {
|
||||
this.spec.addExtension(key, value);
|
||||
this.extensions = { [key]: value, ...this.extensions };
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the extension attributes, if any, associated with this event
|
||||
* @see https://github.com/cloudevents/spec/blob/master/spec.md#extension-context-attributes
|
||||
* @returns {Object} the extensions attributes - if none exist will will be {}
|
||||
*/
|
||||
getExtensions(): object {
|
||||
return this.extensions;
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
export default interface Extensions {
|
||||
[key: string]: any
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
class Base64Parser {
|
||||
decorator: any;
|
||||
|
||||
constructor(decorator: any) {
|
||||
this.decorator = decorator;
|
||||
}
|
||||
|
||||
parse(payload: any): any {
|
||||
let payloadToParse = payload;
|
||||
if (this.decorator) {
|
||||
payloadToParse = this.decorator.parse(payload);
|
||||
}
|
||||
|
||||
return Buffer.from(payloadToParse, "base64").toString();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Base64Parser;
|
|
@ -1,15 +0,0 @@
|
|||
class JSONFormatter {
|
||||
/*
|
||||
* Every internal data structure is JSON by nature, so
|
||||
* no transformation is required
|
||||
*/
|
||||
format(payload: any) {
|
||||
return payload;
|
||||
}
|
||||
|
||||
toString(payload: any) {
|
||||
return JSON.stringify(payload);
|
||||
}
|
||||
}
|
||||
|
||||
export default JSONFormatter;
|
|
@ -0,0 +1,18 @@
|
|||
import { Parser } from "./parser";
|
||||
|
||||
export class Base64Parser implements Parser {
|
||||
decorator?: Parser;
|
||||
|
||||
constructor(decorator?: Parser) {
|
||||
this.decorator = decorator;
|
||||
}
|
||||
|
||||
parse(payload: Record<string, unknown> | string): string {
|
||||
let payloadToParse = payload;
|
||||
if (this.decorator) {
|
||||
payloadToParse = this.decorator.parse(payload) as string;
|
||||
}
|
||||
|
||||
return Buffer.from(payloadToParse as string, "base64").toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import { Parser } from "./parser";
|
||||
|
||||
export class DateParser extends Parser {
|
||||
parse(payload: string): Date {
|
||||
return new Date(Date.parse(payload));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import { Parser } from "./parser";
|
||||
import { JSONParser } from "./json";
|
||||
import { PassThroughParser } from "./pass_through";
|
||||
import { Base64Parser } from "./base64";
|
||||
import CONSTANTS from "../constants";
|
||||
|
||||
export * from "./parser";
|
||||
export * from "./base64";
|
||||
export * from "./json";
|
||||
export * from "./date";
|
||||
export * from "./mapped";
|
||||
export * from "./pass_through";
|
||||
|
||||
export const parserByContentType: { [key: string]: Parser } = {
|
||||
[CONSTANTS.MIME_JSON]: new JSONParser(),
|
||||
[CONSTANTS.MIME_CE_JSON]: new JSONParser(),
|
||||
[CONSTANTS.MIME_OCTET_STREAM]: new PassThroughParser(),
|
||||
};
|
||||
|
||||
export const parserByEncoding: { [key: string]: { [key: string]: Parser } } = {
|
||||
base64: {
|
||||
[CONSTANTS.MIME_CE_JSON]: new JSONParser(new Base64Parser()),
|
||||
[CONSTANTS.MIME_OCTET_STREAM]: new PassThroughParser(),
|
||||
},
|
||||
};
|
|
@ -1,18 +1,15 @@
|
|||
const {
|
||||
isString,
|
||||
isDefinedOrThrow,
|
||||
isStringOrObjectOrThrow
|
||||
} = require("../../bindings/http/validation/fun");
|
||||
const ValidationError = require("../../bindings/http/validation/validation_error.js");
|
||||
import { Base64Parser } from "./base64";
|
||||
import { isString, isDefinedOrThrow, isStringOrObjectOrThrow, ValidationError } from "../event/validation";
|
||||
import { Parser } from "./parser";
|
||||
|
||||
const invalidPayloadTypeError = new ValidationError("invalid payload type, allowed are: string or object");
|
||||
const nullOrUndefinedPayload = new ValidationError("null or undefined payload");
|
||||
|
||||
const asJSON = (v: object|string) => (isString(v) ? JSON.parse(v as string) : v);
|
||||
const parseJSON = (v: Record<string, unknown> | string): string => (isString(v) ? JSON.parse(v as string) : v);
|
||||
|
||||
class JSONParser {
|
||||
decorator: Base64Parser
|
||||
constructor(decorator: Base64Parser) {
|
||||
export class JSONParser implements Parser {
|
||||
decorator?: Base64Parser;
|
||||
constructor(decorator?: Base64Parser) {
|
||||
this.decorator = decorator;
|
||||
}
|
||||
|
||||
|
@ -21,15 +18,13 @@ class JSONParser {
|
|||
* @param {object|string} payload the JSON payload
|
||||
* @return {object} the parsed JSON payload.
|
||||
*/
|
||||
parse(payload: object|string) {
|
||||
parse(payload: Record<string, unknown> | string): string {
|
||||
if (this.decorator) {
|
||||
payload = this.decorator.parse(payload);
|
||||
}
|
||||
|
||||
isDefinedOrThrow(payload, nullOrUndefinedPayload);
|
||||
isStringOrObjectOrThrow(payload, invalidPayloadTypeError);
|
||||
return asJSON(payload);
|
||||
return parseJSON(payload);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = JSONParser;
|
|
@ -0,0 +1,6 @@
|
|||
import { Parser } from "./parser";
|
||||
|
||||
export interface MappedParser {
|
||||
name: string;
|
||||
parser: Parser;
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export abstract class Parser {
|
||||
abstract parse(payload: Record<string, unknown> | string): unknown;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import { Parser } from "./parser";
|
||||
|
||||
export class PassThroughParser extends Parser {
|
||||
parse(payload: unknown): unknown {
|
||||
return payload;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
import { CloudEvent } from "../event";
|
||||
import { emitBinary, emitStructured } from "./http";
|
||||
import { Protocol } from ".";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { Agent } from "http";
|
||||
|
||||
/**
|
||||
* Options supplied to the Emitter when sending an event.
|
||||
* In addition to url and protocol, TransportOptions may
|
||||
* also accept custom options that will be passed to the
|
||||
* Node.js http functions.
|
||||
*/
|
||||
export interface TransportOptions {
|
||||
/**
|
||||
* The endpoint that will receieve the event.
|
||||
* @example http://cncf.example.com/receiver
|
||||
*/
|
||||
url?: string;
|
||||
/**
|
||||
* The network protocol over which the event will be sent.
|
||||
* @example HTTPStructured
|
||||
* @example HTTPBinary
|
||||
*/
|
||||
protocol?: Protocol;
|
||||
|
||||
[key: string]: string | Record<string, unknown> | Protocol | Agent | undefined;
|
||||
}
|
||||
|
||||
interface EmitterFunction {
|
||||
(event: CloudEvent, options: TransportOptions): Promise<AxiosResponse>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A class to send binary and structured CloudEvents to a remote endpoint.
|
||||
* Currently, supported protocols are HTTPBinary and HTTPStructured.
|
||||
*
|
||||
* @see https://github.com/cloudevents/spec/blob/v1.0/http-protocol-binding.md
|
||||
* @see https://github.com/cloudevents/spec/blob/v1.0/http-protocol-binding.md#13-content-modes
|
||||
*/
|
||||
export class Emitter {
|
||||
url?: string;
|
||||
protocol: Protocol;
|
||||
emitter: EmitterFunction;
|
||||
|
||||
constructor(options: TransportOptions = { protocol: Protocol.HTTPBinary }) {
|
||||
this.protocol = options.protocol as Protocol;
|
||||
this.url = options.url;
|
||||
this.emitter = emitBinary;
|
||||
if (this.protocol === Protocol.HTTPStructured) {
|
||||
this.emitter = emitStructured;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the {CloudEvent} to an event receiver over HTTP POST
|
||||
*
|
||||
* @param {CloudEvent} event the CloudEvent to be sent
|
||||
* @param {Object} [options] The configuration options for this event. Options
|
||||
* provided will be passed along to Node.js `http.request()`.
|
||||
* https://nodejs.org/api/http.html#http_http_request_options_callback
|
||||
* @param {string} [options.url] The HTTP/S url that should receive this event.
|
||||
* The URL is optional if one was provided when this emitter was constructed.
|
||||
* In that case, it will be used as the recipient endpoint. The endpoint can
|
||||
* be overridden by providing a URL here.
|
||||
* @returns {Promise} Promise with an eventual response from the receiver
|
||||
*/
|
||||
send(event: CloudEvent, options?: TransportOptions): Promise<AxiosResponse> {
|
||||
options = options || {};
|
||||
options.url = options.url || this.url;
|
||||
if (options.protocol != this.protocol) {
|
||||
if (this.protocol === Protocol.HTTPBinary) return emitBinary(event, options);
|
||||
return emitStructured(event, options);
|
||||
}
|
||||
return this.emitter(event, options);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
import axios, { AxiosRequestConfig, AxiosResponse } from "axios";
|
||||
|
||||
import { CloudEvent, Version } from "../../event";
|
||||
import { TransportOptions } from "../emitter";
|
||||
import { Headers, headersFor } from "./headers";
|
||||
import { asData } from "../../event/validation";
|
||||
import CONSTANTS from "../../constants";
|
||||
|
||||
/**
|
||||
* Send a CloudEvent over HTTP POST to the `options.url` provided.
|
||||
* @param {CloudEvent} event the event to send to the remote endpoint
|
||||
* @param {TransportOptions} options options provided to the transport layer
|
||||
* @returns {Promise<AxiosResponse>} the HTTP response from the transport layer
|
||||
*/
|
||||
export async function emitBinary(event: CloudEvent, options: TransportOptions): Promise<AxiosResponse> {
|
||||
if (event.specversion !== Version.V1 && event.specversion !== Version.V03) {
|
||||
return Promise.reject(`Unknown spec version ${event.specversion}`);
|
||||
}
|
||||
return emit(event, options, headersFor(event));
|
||||
}
|
||||
|
||||
async function emit(event: CloudEvent, options: TransportOptions, headers: Headers): Promise<AxiosResponse> {
|
||||
const contentType: Headers = { [CONSTANTS.HEADER_CONTENT_TYPE]: CONSTANTS.DEFAULT_CE_CONTENT_TYPE };
|
||||
const config = {
|
||||
...options,
|
||||
method: "POST",
|
||||
headers: { ...contentType, ...headers, ...(options.headers as Headers) },
|
||||
data: asData(event.data, event.datacontenttype as string),
|
||||
};
|
||||
return axios.request(config as AxiosRequestConfig);
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
import { CloudEvent, Version } from "../..";
|
||||
import { CloudEventV1, validateV1 } from "../../event/v1";
|
||||
import { CloudEventV03, validateV03 } from "../../event/v03";
|
||||
import { Headers, validate } from "./headers";
|
||||
import { binaryParsers as v1Parsers } from "./v1/parsers";
|
||||
import { binaryParsers as v03Parsers } from "./v03/parsers";
|
||||
import { parserByContentType, MappedParser } from "../../parsers";
|
||||
import { isString, isBase64, ValidationError, isStringOrObjectOrThrow } from "../../event/validation";
|
||||
import CONSTANTS from "../../constants";
|
||||
|
||||
/**
|
||||
* A class that receives binary CloudEvents over HTTP. This class can be used
|
||||
* if you know that all incoming events will be using binary transport. If
|
||||
* events can come as either binary or structured, use {HTTPReceiver}.
|
||||
*/
|
||||
export class BinaryHTTPReceiver {
|
||||
/**
|
||||
* The specification version of the incoming cloud event
|
||||
*/
|
||||
version: Version;
|
||||
constructor(version: Version = Version.V1) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an incoming HTTP request, converting it to a {CloudEvent}
|
||||
* instance if it conforms to the Cloud Event specification for this receiver.
|
||||
*
|
||||
* @param {Object|string} payload the HTTP request body
|
||||
* @param {Object} headers the HTTP request headers
|
||||
* @param {Version} version the spec version of the incoming event
|
||||
* @returns {CloudEvent} an instance of CloudEvent representing the incoming request
|
||||
* @throws {ValidationError} of the event does not conform to the spec
|
||||
*/
|
||||
parse(payload: string | Record<string, unknown>, headers: Headers): CloudEvent {
|
||||
if (!payload) throw new ValidationError("payload is null or undefined");
|
||||
if (!headers) throw new ValidationError("headers is null or undefined");
|
||||
isStringOrObjectOrThrow(payload, new ValidationError("payload must be an object or a string"));
|
||||
|
||||
if (
|
||||
headers[CONSTANTS.CE_HEADERS.SPEC_VERSION] &&
|
||||
headers[CONSTANTS.CE_HEADERS.SPEC_VERSION] !== Version.V03 &&
|
||||
headers[CONSTANTS.CE_HEADERS.SPEC_VERSION] !== Version.V1
|
||||
) {
|
||||
throw new ValidationError(`invalid spec version ${headers[CONSTANTS.CE_HEADERS.SPEC_VERSION]}`);
|
||||
}
|
||||
|
||||
payload = isString(payload) && isBase64(payload) ? Buffer.from(payload as string, "base64").toString() : payload;
|
||||
|
||||
// Clone and low case all headers names
|
||||
const sanitizedHeaders = validate(headers);
|
||||
|
||||
const eventObj: { [key: string]: unknown | string | Record<string, unknown> } = {};
|
||||
const parserMap: Record<string, MappedParser> = this.version === Version.V1 ? v1Parsers : v03Parsers;
|
||||
|
||||
for (const header in parserMap) {
|
||||
if (sanitizedHeaders[header]) {
|
||||
const mappedParser: MappedParser = parserMap[header];
|
||||
eventObj[mappedParser.name] = mappedParser.parser.parse(sanitizedHeaders[header]);
|
||||
delete sanitizedHeaders[header];
|
||||
}
|
||||
}
|
||||
|
||||
const parser = parserByContentType[eventObj.datacontenttype as string];
|
||||
const parsedPayload = parser.parse(payload);
|
||||
|
||||
// Every unprocessed header can be an extension
|
||||
for (const header in sanitizedHeaders) {
|
||||
if (header.startsWith(CONSTANTS.EXTENSIONS_PREFIX)) {
|
||||
eventObj[header.substring(CONSTANTS.EXTENSIONS_PREFIX.length)] = sanitizedHeaders[header];
|
||||
}
|
||||
}
|
||||
|
||||
const cloudevent = new CloudEvent({ ...eventObj, data: parsedPayload } as CloudEventV1 | CloudEventV03);
|
||||
this.version === Version.V1 ? validateV1(cloudevent) : validateV03(cloudevent);
|
||||
return cloudevent;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
import { ValidationError, CloudEvent } from "../..";
|
||||
import { headerMap as v1Map } from "./v1";
|
||||
import { headerMap as v03Map } from "./v03";
|
||||
import { Version } from "../../event";
|
||||
import { MappedParser } from "../../parsers";
|
||||
import CONSTANTS from "../../constants";
|
||||
|
||||
/**
|
||||
* An interface representing HTTP headers as key/value string pairs
|
||||
*/
|
||||
export interface Headers {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
export const allowedContentTypes = [CONSTANTS.MIME_JSON, CONSTANTS.MIME_OCTET_STREAM];
|
||||
export const requiredHeaders = [
|
||||
CONSTANTS.CE_HEADERS.ID,
|
||||
CONSTANTS.CE_HEADERS.SOURCE,
|
||||
CONSTANTS.CE_HEADERS.TYPE,
|
||||
CONSTANTS.CE_HEADERS.SPEC_VERSION,
|
||||
];
|
||||
|
||||
/**
|
||||
* Validates cloud event headers and their values
|
||||
* @param {Headers} headers event transport headers for validation
|
||||
* @throws {ValidationError} if the headers are invalid
|
||||
* @return {boolean} true if headers are valid
|
||||
*/
|
||||
export function validate(headers: Headers): Headers {
|
||||
const sanitizedHeaders = sanitize(headers);
|
||||
|
||||
// if content-type exists, be sure it's an allowed type
|
||||
const contentTypeHeader = sanitizedHeaders[CONSTANTS.HEADER_CONTENT_TYPE];
|
||||
const noContentType = !allowedContentTypes.includes(contentTypeHeader);
|
||||
if (contentTypeHeader && noContentType) {
|
||||
throw new ValidationError("invalid content type", [sanitizedHeaders[CONSTANTS.HEADER_CONTENT_TYPE]]);
|
||||
}
|
||||
|
||||
requiredHeaders
|
||||
.filter((required: string) => !sanitizedHeaders[required])
|
||||
.forEach((required: string) => {
|
||||
throw new ValidationError(`header '${required}' not found`);
|
||||
});
|
||||
|
||||
if (!sanitizedHeaders[CONSTANTS.HEADER_CONTENT_TYPE]) {
|
||||
sanitizedHeaders[CONSTANTS.HEADER_CONTENT_TYPE] = CONSTANTS.MIME_JSON;
|
||||
}
|
||||
|
||||
return sanitizedHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the HTTP headers that will be sent for this event when the HTTP transmission
|
||||
* mode is "binary". Events sent over HTTP in structured mode only have a single CE header
|
||||
* and that is "ce-id", corresponding to the event ID.
|
||||
* @param {CloudEvent} event a CloudEvent
|
||||
* @returns {Object} the headers that will be sent for the event
|
||||
*/
|
||||
export function headersFor(event: CloudEvent): Headers {
|
||||
const headers: Headers = {};
|
||||
let headerMap: Readonly<{ [key: string]: MappedParser }>;
|
||||
if (event.specversion === Version.V1) {
|
||||
headerMap = v1Map;
|
||||
} else {
|
||||
headerMap = v03Map;
|
||||
}
|
||||
|
||||
// iterate over the event properties - generate a header for each
|
||||
Object.getOwnPropertyNames(event).forEach((property) => {
|
||||
const value = event[property];
|
||||
if (value) {
|
||||
const map: MappedParser | undefined = headerMap[property] as MappedParser;
|
||||
if (map) {
|
||||
headers[map.name] = map.parser.parse(value as string) as string;
|
||||
} else if (property !== CONSTANTS.DATA_ATTRIBUTE && property !== `${CONSTANTS.DATA_ATTRIBUTE}_base64`) {
|
||||
headers[`${CONSTANTS.EXTENSIONS_PREFIX}${property}`] = value as string;
|
||||
}
|
||||
}
|
||||
});
|
||||
// Treat time specially, since it's handled with getters and setters in CloudEvent
|
||||
if (event.time) {
|
||||
headers[CONSTANTS.CE_HEADERS.TIME] = event.time as string;
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes incoming headers by lowercasing them and potentially removing
|
||||
* encoding from the content-type header.
|
||||
* @param {Headers} headers HTTP headers as key/value pairs
|
||||
* @returns {Headers} the sanitized headers
|
||||
*/
|
||||
export function sanitize(headers: Headers): Headers {
|
||||
const sanitized: Headers = {};
|
||||
|
||||
Array.from(Object.keys(headers))
|
||||
.filter((header) => Object.hasOwnProperty.call(headers, header))
|
||||
.forEach((header) => (sanitized[header.toLowerCase()] = headers[header]));
|
||||
|
||||
sanitized[CONSTANTS.HEADER_CONTENT_TYPE] = sanitizeContentType(sanitized[CONSTANTS.HEADER_CONTENT_TYPE]) as string;
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
function sanitizeContentType(contentType: string): string | undefined {
|
||||
if (contentType) {
|
||||
return Array.of(contentType)
|
||||
.map((c) => c.split(";"))
|
||||
.map((c) => c.shift())
|
||||
.shift();
|
||||
}
|
||||
return contentType;
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./binary_emitter";
|
||||
export * from "./structured_emitter";
|
|
@ -0,0 +1,20 @@
|
|||
import axios, { AxiosRequestConfig, AxiosResponse } from "axios";
|
||||
import { CloudEvent } from "../../event";
|
||||
import { TransportOptions } from "../emitter";
|
||||
import CONSTANTS from "../../constants";
|
||||
|
||||
const defaults = {
|
||||
headers: {
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: CONSTANTS.DEFAULT_CE_CONTENT_TYPE,
|
||||
},
|
||||
};
|
||||
|
||||
export function emitStructured(event: CloudEvent, options: TransportOptions): Promise<AxiosResponse> {
|
||||
const config = {
|
||||
...defaults,
|
||||
...options,
|
||||
method: "POST",
|
||||
data: event,
|
||||
};
|
||||
return axios.request(config as AxiosRequestConfig);
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
import { CloudEvent, Version } from "../..";
|
||||
import { Headers, sanitize } from "./headers";
|
||||
import { Parser, JSONParser, MappedParser } from "../../parsers";
|
||||
import { parserByContentType } from "../../parsers";
|
||||
import { structuredParsers as v1Parsers } from "./v1/parsers";
|
||||
import { structuredParsers as v03Parsers } from "./v03/parsers";
|
||||
import { isString, isBase64, ValidationError, isStringOrObjectOrThrow } from "../../event/validation";
|
||||
import { CloudEventV1, validateV1 } from "../../event/v1";
|
||||
import { CloudEventV03, validateV03 } from "../../event/v03";
|
||||
import CONSTANTS from "../../constants";
|
||||
|
||||
/**
|
||||
* A utility class used to receive structured CloudEvents
|
||||
* over HTTP.
|
||||
* @see {StructuredReceiver}
|
||||
*/
|
||||
export class StructuredHTTPReceiver {
|
||||
/**
|
||||
* The specification version of the incoming cloud event
|
||||
*/
|
||||
version: Version;
|
||||
constructor(version: Version = Version.V1) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new CloudEvent instance based on the provided payload and headers.
|
||||
*
|
||||
* @param {object} payload the cloud event data payload
|
||||
* @param {object} headers the HTTP headers received for this cloud event
|
||||
* @returns {CloudEvent} a new CloudEvent instance for the provided headers and payload
|
||||
* @throws {ValidationError} if the payload and header combination do not conform to the spec
|
||||
*/
|
||||
parse(payload: Record<string, unknown> | string, headers: Headers): CloudEvent {
|
||||
if (!payload) throw new ValidationError("payload is null or undefined");
|
||||
if (!headers) throw new ValidationError("headers is null or undefined");
|
||||
isStringOrObjectOrThrow(payload, new ValidationError("payload must be an object or a string"));
|
||||
|
||||
if (
|
||||
headers[CONSTANTS.CE_HEADERS.SPEC_VERSION] &&
|
||||
headers[CONSTANTS.CE_HEADERS.SPEC_VERSION] != Version.V03 &&
|
||||
headers[CONSTANTS.CE_HEADERS.SPEC_VERSION] != Version.V1
|
||||
) {
|
||||
throw new ValidationError(`invalid spec version ${headers[CONSTANTS.CE_HEADERS.SPEC_VERSION]}`);
|
||||
}
|
||||
|
||||
payload = isString(payload) && isBase64(payload) ? Buffer.from(payload as string, "base64").toString() : payload;
|
||||
|
||||
// Clone and low case all headers names
|
||||
const sanitizedHeaders = sanitize(headers);
|
||||
|
||||
const contentType = sanitizedHeaders[CONSTANTS.HEADER_CONTENT_TYPE];
|
||||
const parser: Parser = contentType ? parserByContentType[contentType] : new JSONParser();
|
||||
if (!parser) throw new ValidationError(`invalid content type ${sanitizedHeaders[CONSTANTS.HEADER_CONTENT_TYPE]}`);
|
||||
const incoming = { ...(parser.parse(payload) as Record<string, unknown>) };
|
||||
|
||||
const eventObj: { [key: string]: unknown } = {};
|
||||
const parserMap: Record<string, MappedParser> = this.version === Version.V1 ? v1Parsers : v03Parsers;
|
||||
|
||||
for (const key in parserMap) {
|
||||
const property = incoming[key];
|
||||
if (property) {
|
||||
const parser: MappedParser = parserMap[key];
|
||||
eventObj[parser.name] = parser.parser.parse(property as string);
|
||||
}
|
||||
delete incoming[key];
|
||||
}
|
||||
|
||||
// extensions are what we have left after processing all other properties
|
||||
for (const key in incoming) {
|
||||
eventObj[key] = incoming[key];
|
||||
}
|
||||
|
||||
// ensure data content is correctly encoded
|
||||
if (eventObj.data && eventObj.datacontentencoding) {
|
||||
if (eventObj.datacontentencoding === CONSTANTS.ENCODING_BASE64 && !isBase64(eventObj.data)) {
|
||||
throw new ValidationError("invalid payload");
|
||||
}
|
||||
}
|
||||
|
||||
const cloudevent = new CloudEvent(eventObj as CloudEventV1 | CloudEventV03);
|
||||
|
||||
// Validates the event
|
||||
this.version === Version.V1 ? validateV1(cloudevent) : validateV03(cloudevent);
|
||||
return cloudevent;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import { PassThroughParser, MappedParser } from "../../../parsers";
|
||||
import CONSTANTS from "../../../constants";
|
||||
|
||||
const passThrough = new PassThroughParser();
|
||||
function parser(header: string, parser = passThrough): MappedParser {
|
||||
return { name: header, parser };
|
||||
}
|
||||
|
||||
/**
|
||||
* A utility Map used to retrieve the header names for a CloudEvent
|
||||
* using the CloudEvent getter function.
|
||||
*/
|
||||
export const headerMap: Readonly<{ [key: string]: MappedParser }> = Object.freeze({
|
||||
[CONSTANTS.CE_ATTRIBUTES.CONTENT_TYPE]: parser(CONSTANTS.HEADER_CONTENT_TYPE),
|
||||
[CONSTANTS.CE_ATTRIBUTES.SUBJECT]: parser(CONSTANTS.CE_HEADERS.SUBJECT),
|
||||
[CONSTANTS.CE_ATTRIBUTES.TYPE]: parser(CONSTANTS.CE_HEADERS.TYPE),
|
||||
[CONSTANTS.CE_ATTRIBUTES.SPEC_VERSION]: parser(CONSTANTS.CE_HEADERS.SPEC_VERSION),
|
||||
[CONSTANTS.CE_ATTRIBUTES.SOURCE]: parser(CONSTANTS.CE_HEADERS.SOURCE),
|
||||
[CONSTANTS.CE_ATTRIBUTES.ID]: parser(CONSTANTS.CE_HEADERS.ID),
|
||||
[CONSTANTS.CE_ATTRIBUTES.TIME]: parser(CONSTANTS.CE_HEADERS.TIME),
|
||||
[CONSTANTS.STRUCTURED_ATTRS_03.CONTENT_ENCODING]: parser(CONSTANTS.BINARY_HEADERS_03.CONTENT_ENCODING),
|
||||
[CONSTANTS.STRUCTURED_ATTRS_03.SCHEMA_URL]: parser(CONSTANTS.BINARY_HEADERS_03.SCHEMA_URL),
|
||||
});
|
|
@ -0,0 +1 @@
|
|||
export * from "./headers";
|
|
@ -0,0 +1,34 @@
|
|||
import { MappedParser, DateParser, PassThroughParser } from "../../../parsers";
|
||||
import CONSTANTS from "../../../constants";
|
||||
|
||||
const passThrough = new PassThroughParser();
|
||||
function parser(name: string, parser = passThrough): MappedParser {
|
||||
return { name: name, parser: parser };
|
||||
}
|
||||
|
||||
const binaryParsers: Record<string, MappedParser> = Object.freeze({
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: parser(CONSTANTS.CE_ATTRIBUTES.TYPE),
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: parser(CONSTANTS.CE_ATTRIBUTES.SPEC_VERSION),
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: parser(CONSTANTS.CE_ATTRIBUTES.SOURCE),
|
||||
[CONSTANTS.CE_HEADERS.ID]: parser(CONSTANTS.CE_ATTRIBUTES.ID),
|
||||
[CONSTANTS.CE_HEADERS.TIME]: parser(CONSTANTS.CE_ATTRIBUTES.TIME, new DateParser()),
|
||||
[CONSTANTS.BINARY_HEADERS_03.SCHEMA_URL]: parser(CONSTANTS.STRUCTURED_ATTRS_03.SCHEMA_URL),
|
||||
[CONSTANTS.CE_HEADERS.SUBJECT]: parser(CONSTANTS.CE_ATTRIBUTES.SUBJECT),
|
||||
[CONSTANTS.BINARY_HEADERS_03.CONTENT_ENCODING]: parser(CONSTANTS.STRUCTURED_ATTRS_03.CONTENT_ENCODING),
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: parser(CONSTANTS.CE_ATTRIBUTES.CONTENT_TYPE),
|
||||
});
|
||||
|
||||
const structuredParsers: Record<string, MappedParser> = Object.freeze({
|
||||
[CONSTANTS.CE_ATTRIBUTES.TYPE]: parser(CONSTANTS.CE_ATTRIBUTES.TYPE),
|
||||
[CONSTANTS.CE_ATTRIBUTES.SPEC_VERSION]: parser(CONSTANTS.CE_ATTRIBUTES.SPEC_VERSION),
|
||||
[CONSTANTS.CE_ATTRIBUTES.SOURCE]: parser(CONSTANTS.CE_ATTRIBUTES.SOURCE),
|
||||
[CONSTANTS.CE_ATTRIBUTES.ID]: parser(CONSTANTS.CE_ATTRIBUTES.ID),
|
||||
[CONSTANTS.CE_ATTRIBUTES.TIME]: parser(CONSTANTS.CE_ATTRIBUTES.TIME, new DateParser()),
|
||||
[CONSTANTS.STRUCTURED_ATTRS_03.SCHEMA_URL]: parser(CONSTANTS.STRUCTURED_ATTRS_03.SCHEMA_URL),
|
||||
[CONSTANTS.STRUCTURED_ATTRS_03.CONTENT_ENCODING]: parser(CONSTANTS.STRUCTURED_ATTRS_03.CONTENT_ENCODING),
|
||||
[CONSTANTS.CE_ATTRIBUTES.CONTENT_TYPE]: parser(CONSTANTS.CE_ATTRIBUTES.CONTENT_TYPE),
|
||||
[CONSTANTS.CE_ATTRIBUTES.SUBJECT]: parser(CONSTANTS.CE_ATTRIBUTES.SUBJECT),
|
||||
[CONSTANTS.CE_ATTRIBUTES.DATA]: parser(CONSTANTS.CE_ATTRIBUTES.DATA),
|
||||
});
|
||||
|
||||
export { binaryParsers, structuredParsers };
|
|
@ -0,0 +1,22 @@
|
|||
import { MappedParser, PassThroughParser } from "../../../parsers";
|
||||
import CONSTANTS from "../../../constants";
|
||||
|
||||
const passThrough = new PassThroughParser();
|
||||
function parser(header: string, parser = passThrough): MappedParser {
|
||||
return { name: header, parser };
|
||||
}
|
||||
|
||||
/**
|
||||
* A utility Map used to retrieve the header names for a CloudEvent
|
||||
* using the CloudEvent getter function.
|
||||
*/
|
||||
export const headerMap: Readonly<{ [key: string]: MappedParser }> = Object.freeze({
|
||||
[CONSTANTS.CE_ATTRIBUTES.CONTENT_TYPE]: parser(CONSTANTS.HEADER_CONTENT_TYPE),
|
||||
[CONSTANTS.CE_ATTRIBUTES.SUBJECT]: parser(CONSTANTS.CE_HEADERS.SUBJECT),
|
||||
[CONSTANTS.CE_ATTRIBUTES.TYPE]: parser(CONSTANTS.CE_HEADERS.TYPE),
|
||||
[CONSTANTS.CE_ATTRIBUTES.SPEC_VERSION]: parser(CONSTANTS.CE_HEADERS.SPEC_VERSION),
|
||||
[CONSTANTS.CE_ATTRIBUTES.SOURCE]: parser(CONSTANTS.CE_HEADERS.SOURCE),
|
||||
[CONSTANTS.CE_ATTRIBUTES.ID]: parser(CONSTANTS.CE_HEADERS.ID),
|
||||
[CONSTANTS.CE_ATTRIBUTES.TIME]: parser(CONSTANTS.CE_HEADERS.TIME),
|
||||
[CONSTANTS.STRUCTURED_ATTRS_1.DATA_SCHEMA]: parser(CONSTANTS.BINARY_HEADERS_1.DATA_SCHEMA),
|
||||
});
|
|
@ -0,0 +1 @@
|
|||
export * from "./headers";
|
|
@ -0,0 +1,35 @@
|
|||
import { PassThroughParser, DateParser, MappedParser } from "../../../parsers";
|
||||
import CONSTANTS from "../../../constants";
|
||||
|
||||
const passThrough = new PassThroughParser();
|
||||
|
||||
function parser(name: string, parser = passThrough): MappedParser {
|
||||
return { name: name, parser: parser };
|
||||
}
|
||||
|
||||
const binaryParsers: Record<string, MappedParser> = Object.freeze({
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: parser(CONSTANTS.CE_ATTRIBUTES.TYPE),
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: parser(CONSTANTS.CE_ATTRIBUTES.SPEC_VERSION),
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: parser(CONSTANTS.CE_ATTRIBUTES.SOURCE),
|
||||
[CONSTANTS.CE_HEADERS.ID]: parser(CONSTANTS.CE_ATTRIBUTES.ID),
|
||||
[CONSTANTS.CE_HEADERS.TIME]: parser(CONSTANTS.CE_ATTRIBUTES.TIME, new DateParser()),
|
||||
[CONSTANTS.BINARY_HEADERS_1.DATA_SCHEMA]: parser(CONSTANTS.STRUCTURED_ATTRS_1.DATA_SCHEMA),
|
||||
[CONSTANTS.CE_HEADERS.SUBJECT]: parser(CONSTANTS.CE_ATTRIBUTES.SUBJECT),
|
||||
[CONSTANTS.CE_ATTRIBUTES.CONTENT_TYPE]: parser(CONSTANTS.CE_ATTRIBUTES.CONTENT_TYPE),
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: parser(CONSTANTS.CE_ATTRIBUTES.CONTENT_TYPE),
|
||||
});
|
||||
|
||||
const structuredParsers: Record<string, MappedParser> = Object.freeze({
|
||||
[CONSTANTS.CE_ATTRIBUTES.TYPE]: parser(CONSTANTS.CE_ATTRIBUTES.TYPE),
|
||||
[CONSTANTS.CE_ATTRIBUTES.SPEC_VERSION]: parser(CONSTANTS.CE_ATTRIBUTES.SPEC_VERSION),
|
||||
[CONSTANTS.CE_ATTRIBUTES.SOURCE]: parser(CONSTANTS.CE_ATTRIBUTES.SOURCE),
|
||||
[CONSTANTS.CE_ATTRIBUTES.ID]: parser(CONSTANTS.CE_ATTRIBUTES.ID),
|
||||
[CONSTANTS.CE_ATTRIBUTES.TIME]: parser(CONSTANTS.CE_ATTRIBUTES.TIME, new DateParser()),
|
||||
[CONSTANTS.STRUCTURED_ATTRS_1.DATA_SCHEMA]: parser(CONSTANTS.STRUCTURED_ATTRS_1.DATA_SCHEMA),
|
||||
[CONSTANTS.CE_ATTRIBUTES.CONTENT_TYPE]: parser(CONSTANTS.CE_ATTRIBUTES.CONTENT_TYPE),
|
||||
[CONSTANTS.CE_ATTRIBUTES.SUBJECT]: parser(CONSTANTS.CE_ATTRIBUTES.SUBJECT),
|
||||
[CONSTANTS.CE_ATTRIBUTES.DATA]: parser(CONSTANTS.CE_ATTRIBUTES.DATA),
|
||||
[CONSTANTS.STRUCTURED_ATTRS_1.DATA_BASE64]: parser(CONSTANTS.STRUCTURED_ATTRS_1.DATA_BASE64),
|
||||
});
|
||||
|
||||
export { structuredParsers, binaryParsers };
|
|
@ -0,0 +1,6 @@
|
|||
export * from "./emitter";
|
||||
export * from "./receiver";
|
||||
export * as v1Headers from "./http/v1";
|
||||
export * as v03Headers from "./http/v03";
|
||||
export * as http from "./http/headers";
|
||||
export * from "./protocols";
|
|
@ -0,0 +1,8 @@
|
|||
/**
|
||||
* An enum representing the transport protocols for an event
|
||||
*/
|
||||
export const enum Protocol {
|
||||
HTTPBinary,
|
||||
HTTPStructured,
|
||||
HTTP,
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
import { Headers } from "./http/headers";
|
||||
import { CloudEvent, Version, ValidationError } from "..";
|
||||
import { BinaryHTTPReceiver as BinaryReceiver } from "./http/binary_receiver";
|
||||
import { StructuredHTTPReceiver as StructuredReceiver } from "./http/structured_receiver";
|
||||
import { CloudEventV03 } from "../event/v03";
|
||||
import { CloudEventV1 } from "../event/v1";
|
||||
import { Protocol } from "./protocols";
|
||||
import CONSTANTS from "../constants";
|
||||
|
||||
/**
|
||||
* An enum representing the two HTTP transport modes, binary and structured
|
||||
*/
|
||||
export enum Mode {
|
||||
BINARY = "binary",
|
||||
STRUCTURED = "structured",
|
||||
}
|
||||
|
||||
/**
|
||||
* A class to receive a CloudEvent from an HTTP POST request.
|
||||
*/
|
||||
export class Receiver {
|
||||
protocol: Protocol;
|
||||
receivers: {
|
||||
v1: {
|
||||
structured: StructuredReceiver;
|
||||
binary: BinaryReceiver;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
v03: {
|
||||
structured: StructuredReceiver;
|
||||
binary: BinaryReceiver;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Create an instance of an HTTPReceiver to accept incoming CloudEvents.
|
||||
* @param {Protocol} protocol the transport protocol - currently only Protocol.HTTP is supported
|
||||
*/
|
||||
constructor(protocol: Protocol = Protocol.HTTP) {
|
||||
// currently unused, but reserved for future protocol implementations
|
||||
this.protocol = protocol;
|
||||
this.receivers = {
|
||||
v1: {
|
||||
structured: new StructuredReceiver(Version.V1),
|
||||
binary: new BinaryReceiver(Version.V1),
|
||||
},
|
||||
v03: {
|
||||
structured: new StructuredReceiver(Version.V03),
|
||||
binary: new BinaryReceiver(Version.V03),
|
||||
},
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Acceptor for an incoming HTTP CloudEvent POST. Can process
|
||||
* binary and structured incoming CloudEvents.
|
||||
*
|
||||
* @param {Object} headers HTTP headers keyed by header name ("Content-Type")
|
||||
* @param {Object|JSON} body The body of the HTTP request
|
||||
* @return {CloudEvent} A new {CloudEvent} instance
|
||||
*/
|
||||
accept(headers: Headers, body: string | Record<string, unknown> | CloudEventV1 | CloudEventV03): CloudEvent {
|
||||
const mode: Mode = getMode(headers);
|
||||
const version = getVersion(mode, headers, body);
|
||||
switch (version) {
|
||||
case Version.V1:
|
||||
return this.receivers.v1[mode].parse(body, headers);
|
||||
case Version.V03:
|
||||
return this.receivers.v03[mode].parse(body, headers);
|
||||
default:
|
||||
console.error(`Unknown spec version ${version}. Default to ${Version.V1}`);
|
||||
return this.receivers.v1[mode].parse(body, headers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the HTTP transport mode (binary or structured) based
|
||||
* on the incoming HTTP headers.
|
||||
* @param {Headers} headers the incoming HTTP headers
|
||||
* @returns {Mode} the transport mode
|
||||
*/
|
||||
function getMode(headers: Headers): Mode {
|
||||
const contentType = headers[CONSTANTS.HEADER_CONTENT_TYPE];
|
||||
if (contentType && contentType.startsWith(CONSTANTS.MIME_CE)) {
|
||||
return Mode.STRUCTURED;
|
||||
}
|
||||
if (headers[CONSTANTS.CE_HEADERS.ID]) {
|
||||
return Mode.BINARY;
|
||||
}
|
||||
throw new ValidationError("no cloud event detected");
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the version of an incoming CloudEvent based on the
|
||||
* HTTP headers or HTTP body, depending on transport mode.
|
||||
* @param {Mode} mode the HTTP transport mode
|
||||
* @param {Headers} headers the incoming HTTP headers
|
||||
* @param {Record<string, unknown>} body the HTTP request body
|
||||
* @returns {Version} the CloudEvent specification version
|
||||
*/
|
||||
function getVersion(
|
||||
mode: Mode,
|
||||
headers: Headers,
|
||||
body: string | Record<string, unknown> | CloudEventV03 | CloudEventV1,
|
||||
) {
|
||||
if (mode === Mode.BINARY) {
|
||||
// Check the headers for the version
|
||||
const versionHeader = headers[CONSTANTS.CE_HEADERS.SPEC_VERSION];
|
||||
if (versionHeader) {
|
||||
return versionHeader;
|
||||
}
|
||||
} else {
|
||||
// structured mode - the version is in the body
|
||||
return typeof body === "string" ? JSON.parse(body).specversion : body.specversion;
|
||||
}
|
||||
return Version.V1;
|
||||
}
|
|
@ -1,214 +0,0 @@
|
|||
import { expect } from "chai";
|
||||
import { CloudEvent } from "../";
|
||||
import { CloudEventV03Attributes } from "../lib/v03";
|
||||
import { CloudEventV1Attributes } from "../lib/v1";
|
||||
import Extensions from "../lib/extensions";
|
||||
|
||||
const { SPEC_V1, SPEC_V03 } = require("../lib/bindings/http/constants");
|
||||
|
||||
interface Message {
|
||||
type: string;
|
||||
subject: string;
|
||||
data: any;
|
||||
source: string;
|
||||
dataContentType: string;
|
||||
}
|
||||
|
||||
const type = "org.cncf.cloudevents.example";
|
||||
const source = "http://unit.test";
|
||||
|
||||
const message: Message = {
|
||||
type,
|
||||
source,
|
||||
subject: "greeting",
|
||||
data: {
|
||||
hello: "world"
|
||||
},
|
||||
dataContentType: "application/json"
|
||||
};
|
||||
|
||||
const fixture: CloudEventV1Attributes|CloudEventV03Attributes = {
|
||||
source,
|
||||
type
|
||||
};
|
||||
|
||||
describe("A CloudEvent", () => {
|
||||
it("Can be constructed with a typed Message", () => {
|
||||
const ce = new CloudEvent(message);
|
||||
expect(ce.type).to.equal(type);
|
||||
expect(ce.source).to.equal(source);
|
||||
});
|
||||
});
|
||||
|
||||
describe("A 1.0 CloudEvent", () => {
|
||||
it("has retreivable source and type attributes", () => {
|
||||
const ce = new CloudEvent(fixture);
|
||||
expect(ce.source).to.equal("http://unit.test");
|
||||
expect(ce.type).to.equal("org.cncf.cloudevents.example");
|
||||
});
|
||||
|
||||
it("defaults to specversion 1.0", () => {
|
||||
const ce = new CloudEvent(fixture);
|
||||
expect(ce.specversion).to.equal("1.0");
|
||||
});
|
||||
|
||||
it("generates an ID if one is not provided in the constructor", () => {
|
||||
const ce = new CloudEvent(fixture);
|
||||
expect(ce.id).to.not.be.empty;
|
||||
})
|
||||
|
||||
it("can be created with the specversion SPEC_V1", () => {
|
||||
const ce = new CloudEvent({ specversion: SPEC_V1, ...fixture });
|
||||
expect(ce.specversion).to.equal(SPEC_V1);
|
||||
});
|
||||
|
||||
it("can be constructed with an ID", () => {
|
||||
const ce = new CloudEvent({ id: 1234, ...fixture });
|
||||
expect(ce.id).to.equal(1234);
|
||||
});
|
||||
|
||||
it("generates a timestamp by default", () => {
|
||||
const ce = new CloudEvent(fixture);
|
||||
expect(ce.time).to.not.be.empty;
|
||||
});
|
||||
|
||||
it("can be constructed with a timestamp", () => {
|
||||
const time = new Date();
|
||||
const ce = new CloudEvent({ time, ...fixture });
|
||||
expect(ce.time).to.equal(time.toISOString());
|
||||
});
|
||||
|
||||
it("can be constructed with a dataContentType", () => {
|
||||
const ce = new CloudEvent({ dataContentType: "application/json", ...fixture });
|
||||
expect(ce.dataContentType).to.equal("application/json");
|
||||
});
|
||||
|
||||
it("can be constructed with a dataSchema", () => {
|
||||
const ce = new CloudEvent({ dataSchema: "http://my.schema", ...fixture });
|
||||
expect(ce.dataSchema).to.equal("http://my.schema");
|
||||
});
|
||||
|
||||
it("can be constructed with a subject", () => {
|
||||
const ce = new CloudEvent({ subject: "science", ...fixture });
|
||||
expect(ce.subject).to.equal("science");
|
||||
});
|
||||
|
||||
// Handle deprecated attribute - should this really throw?
|
||||
it("throws a TypeError when constructed with a schemaurl", () => {
|
||||
expect(() => { new CloudEvent({ schemaURL: "http://throw.com", ...fixture }); })
|
||||
.to.throw(TypeError, "cannot set schemaURL on version 1.0 event");
|
||||
});
|
||||
|
||||
// Handle deprecated attribute - should this really throw?
|
||||
it("throws a TypeError when getting a schemaURL", () => {
|
||||
const ce = new CloudEvent(fixture);
|
||||
expect(() => { ce.schemaURL; })
|
||||
.to.throw(TypeError, "cannot get schemaURL from version 1.0 event");
|
||||
});
|
||||
|
||||
it("can be constructed with data", () => {
|
||||
const data = { lunch: "tacos" };
|
||||
const ce = new CloudEvent({
|
||||
data, ...fixture
|
||||
});
|
||||
expect(ce.data).to.equal(data);
|
||||
});
|
||||
|
||||
it("has extensions as an empty object by default", () => {
|
||||
const ce = new CloudEvent(fixture);
|
||||
expect(ce.extensions).to.be.an('object')
|
||||
expect(Object.keys(ce.extensions).length).to.equal(0);
|
||||
});
|
||||
|
||||
it("can be constructed with extensions", () => {
|
||||
const extensions: Extensions = {
|
||||
"extension-key": "extension-value"
|
||||
};
|
||||
const ce = new CloudEvent({
|
||||
extensions, ...fixture
|
||||
});
|
||||
expect(Object.keys(ce.extensions).length).to.equal(1);
|
||||
expect(ce.extensions["extension-key"]).to.equal(extensions["extension-key"]);
|
||||
});
|
||||
|
||||
it("throws ValidationError if the CloudEvent does not conform to the schema");
|
||||
it("returns a JSON string even if format is invalid");
|
||||
it("correctly formats a CloudEvent as JSON");
|
||||
});
|
||||
|
||||
|
||||
describe("A 0.3 CloudEvent", () => {
|
||||
const v03fixture: CloudEventV03Attributes = { specversion: SPEC_V03, ...fixture };
|
||||
|
||||
it("has retreivable source and type attributes", () => {
|
||||
const ce = new CloudEvent(v03fixture);
|
||||
expect(ce.source).to.equal("http://unit.test");
|
||||
expect(ce.type).to.equal("org.cncf.cloudevents.example");
|
||||
});
|
||||
|
||||
it("generates an ID if one is not provided in the constructor", () => {
|
||||
const ce = new CloudEvent(v03fixture);
|
||||
expect(ce.id).to.not.be.empty;
|
||||
})
|
||||
|
||||
it("can be constructed with an ID", () => {
|
||||
const ce = new CloudEvent({ id: 1234, ...v03fixture });
|
||||
expect(ce.id).to.equal(1234);
|
||||
});
|
||||
|
||||
it("generates a timestamp by default", () => {
|
||||
const ce = new CloudEvent(v03fixture);
|
||||
expect(ce.time).to.not.be.empty;
|
||||
});
|
||||
|
||||
it("can be constructed with a timestamp", () => {
|
||||
const time = new Date();
|
||||
const ce = new CloudEvent({ time, ...v03fixture });
|
||||
expect(ce.time).to.equal(time.toISOString());
|
||||
});
|
||||
|
||||
it("can be constructed with a dataContentType", () => {
|
||||
const ce = new CloudEvent({ dataContentType: "application/json", ...v03fixture });
|
||||
expect(ce.dataContentType).to.equal("application/json");
|
||||
});
|
||||
|
||||
it("can be constructed with a dataContentEncoding", () => {
|
||||
const ce = new CloudEvent({ dataContentEncoding: "Base64", ...v03fixture });
|
||||
expect(ce.dataContentEncoding).to.equal("Base64");
|
||||
});
|
||||
|
||||
it("can be constructed with a schemaURL", () => {
|
||||
const ce = new CloudEvent({ schemaURL: "http://my.schema", ...v03fixture });
|
||||
expect(ce.schemaURL).to.equal("http://my.schema");
|
||||
});
|
||||
|
||||
it("can be constructed with a subject", () => {
|
||||
const ce = new CloudEvent({ subject: "science", ...v03fixture });
|
||||
expect(ce.subject).to.equal("science");
|
||||
});
|
||||
|
||||
// Handle 1.0 attribute - should this really throw?
|
||||
it("throws a TypeError when constructed with a dataSchema", () => {
|
||||
expect(() => { new CloudEvent({ dataSchema: "http://throw.com", ...v03fixture }); })
|
||||
.to.throw(TypeError, "cannot set dataSchema on version 0.3 event");
|
||||
});
|
||||
|
||||
// Handle deprecated attribute - should this really throw?
|
||||
it("throws a TypeError when getting a dataSchema", () => {
|
||||
const ce = new CloudEvent(v03fixture);
|
||||
expect(() => { ce.dataSchema; })
|
||||
.to.throw(TypeError, "cannot get dataSchema from version 0.3 event");
|
||||
});
|
||||
|
||||
it("can be constructed with data", () => {
|
||||
const data = { lunch: "tacos" };
|
||||
const ce = new CloudEvent({
|
||||
data, ...v03fixture
|
||||
});
|
||||
expect(ce.data).to.equal(data);
|
||||
});
|
||||
|
||||
it("throws ValidationError if the CloudEvent does not conform to the schema");
|
||||
it("returns a JSON string even if format is invalid");
|
||||
it("correctly formats a CloudEvent as JSON");
|
||||
});
|
|
@ -1,190 +0,0 @@
|
|||
import "mocha";
|
||||
import { expect } from "chai";
|
||||
import nock from "nock";
|
||||
|
||||
const {
|
||||
SPEC_V1,
|
||||
SPEC_V03,
|
||||
DEFAULT_CE_CONTENT_TYPE,
|
||||
BINARY_HEADERS_03,
|
||||
BINARY_HEADERS_1
|
||||
} = require("../lib/bindings/http/constants");
|
||||
|
||||
import { CloudEvent, HTTPEmitter } from "..";
|
||||
|
||||
const receiver:string = "https://cloudevents.io/";
|
||||
const type:string = "com.example.test";
|
||||
const source:string = "urn:event:from:myapi/resource/123";
|
||||
const ext1Name:string = "lunch";
|
||||
const ext1Value:string = "tacos";
|
||||
const ext2Name:string = "supper";
|
||||
const ext2Value:string = "sushi";
|
||||
|
||||
const data = {
|
||||
lunchBreak: "noon"
|
||||
};
|
||||
|
||||
describe("HTTP Transport Binding Emitter for CloudEvents", () => {
|
||||
beforeEach(() => {
|
||||
nock(receiver)
|
||||
.post("/")
|
||||
.reply(function (uri, requestBody: {}) {
|
||||
// return the request body and the headers so they can be
|
||||
// examined in the test
|
||||
if (typeof requestBody === "string") {
|
||||
requestBody = JSON.parse(requestBody);
|
||||
}
|
||||
const returnBody = { ...requestBody, ...this.req.headers };
|
||||
return [
|
||||
201,
|
||||
returnBody
|
||||
];
|
||||
});
|
||||
});
|
||||
|
||||
describe("V1", () => {
|
||||
const emitter = new HTTPEmitter({ url: receiver });
|
||||
const event = new CloudEvent({
|
||||
specversion: SPEC_V1,
|
||||
type,
|
||||
source,
|
||||
time: new Date(),
|
||||
data
|
||||
});
|
||||
event.addExtension(ext1Name, ext1Value)
|
||||
event.addExtension(ext2Name, ext2Value);
|
||||
|
||||
it("Sends a binary 1.0 CloudEvent by default", () => {
|
||||
emitter.send(event).then((response: { data: { [k: string]: string } }) => {
|
||||
// A binary message will have a ce-id header
|
||||
expect(response.data[BINARY_HEADERS_1.ID]).to.equal(event.id);
|
||||
expect(response.data[BINARY_HEADERS_1.SPEC_VERSION]).to.equal(SPEC_V1);
|
||||
// A binary message will have a request body for the data
|
||||
expect(response.data.lunchBreak).to.equal(data.lunchBreak);
|
||||
}).catch(expect.fail);
|
||||
});
|
||||
|
||||
it("Provides the HTTP headers for a binary event", () => {
|
||||
const headers = HTTPEmitter.headers(event);
|
||||
expect(headers[BINARY_HEADERS_1.TYPE]).to.equal(event.type);
|
||||
expect(headers[BINARY_HEADERS_1.SPEC_VERSION]).to.equal(event.specversion);
|
||||
expect(headers[BINARY_HEADERS_1.SOURCE]).to.equal(event.source);
|
||||
expect(headers[BINARY_HEADERS_1.ID]).to.equal(event.id);
|
||||
expect(headers[BINARY_HEADERS_1.TIME]).to.equal(event.time);
|
||||
});
|
||||
|
||||
it("Sends a structured 1.0 CloudEvent if specified", () => {
|
||||
emitter.send(event, { mode: "structured" })
|
||||
.then((response: { data: { [k: string]: string | {}, data: { lunchBreak: string } } }) => {
|
||||
// A structured message will have a cloud event content type
|
||||
expect(response.data["content-type"]).to.equal(DEFAULT_CE_CONTENT_TYPE);
|
||||
// Ensure other CE headers don't exist - just testing for ID
|
||||
expect(response.data[BINARY_HEADERS_1.ID]).to.equal(undefined);
|
||||
// The spec version would have been specified in the body
|
||||
expect(response.data.specversion).to.equal(SPEC_V1);
|
||||
expect(response.data.data.lunchBreak).to.equal(data.lunchBreak);
|
||||
}).catch(expect.fail);
|
||||
});
|
||||
|
||||
it("Sends to an alternate URL if specified", () => {
|
||||
nock(receiver)
|
||||
.post("/alternate")
|
||||
.reply(function (uri, requestBody: {}) {
|
||||
// return the request body and the headers so they can be
|
||||
// examined in the test
|
||||
if (typeof requestBody === "string") {
|
||||
requestBody = JSON.parse(requestBody);
|
||||
}
|
||||
const returnBody = { ...requestBody, ...this.req.headers };
|
||||
return [
|
||||
201,
|
||||
returnBody
|
||||
];
|
||||
});
|
||||
|
||||
emitter.send(event, { mode: "structured", url: `${receiver}alternate` })
|
||||
.then((response: { [k: string]: string | {}, data: { [k: string]: string | {}, specversion: string, data: { lunchBreak: string }} }) => {
|
||||
// A structured message will have a cloud event content type
|
||||
expect(response.data["content-type"]).to.equal(DEFAULT_CE_CONTENT_TYPE);
|
||||
// Ensure other CE headers don't exist - just testing for ID
|
||||
expect(response.data[BINARY_HEADERS_1.ID]).to.equal(undefined);
|
||||
// The spec version would have been specified in the body
|
||||
expect(response.data.specversion).to.equal(SPEC_V1);
|
||||
expect(response.data.data.lunchBreak).to.equal(data.lunchBreak);
|
||||
}).catch(expect.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe("V03", () => {
|
||||
const emitter = new HTTPEmitter({ url: receiver, version: SPEC_V03 });
|
||||
const event = new CloudEvent({
|
||||
specversion: SPEC_V03,
|
||||
type,
|
||||
source,
|
||||
time: new Date(),
|
||||
data
|
||||
});
|
||||
event.addExtension(ext1Name, ext1Value)
|
||||
event.addExtension(ext2Name, ext2Value);
|
||||
|
||||
it("Sends a binary 0.3 CloudEvent", () => {
|
||||
emitter.send(event).then((response: { data: { lunchBreak: string, [k:string]: string }}) => {
|
||||
// A binary message will have a ce-id header
|
||||
expect(response.data[BINARY_HEADERS_03.ID]).to.equal(event.id);
|
||||
expect(response.data[BINARY_HEADERS_03.SPEC_VERSION]).to.equal(SPEC_V03);
|
||||
// A binary message will have a request body for the data
|
||||
expect(response.data.lunchBreak).to.equal(data.lunchBreak);
|
||||
}).catch(expect.fail);
|
||||
});
|
||||
|
||||
it("Provides the HTTP headers for a binary event", () => {
|
||||
const headers = HTTPEmitter.headers(event, SPEC_V03);
|
||||
expect(headers[BINARY_HEADERS_03.TYPE]).to.equal(event.type);
|
||||
expect(headers[BINARY_HEADERS_03.SPEC_VERSION]).to.equal(event.specversion);
|
||||
expect(headers[BINARY_HEADERS_03.SOURCE]).to.equal(event.source);
|
||||
expect(headers[BINARY_HEADERS_03.ID]).to.equal(event.id);
|
||||
expect(headers[BINARY_HEADERS_03.TIME]).to.equal(event.time);
|
||||
});
|
||||
|
||||
it("Sends a structured 0.3 CloudEvent if specified", () => {
|
||||
emitter.send(event, { mode: "structured" })
|
||||
.then((response: { data: { [k:string]: any, specversion: string, data: { lunchBreak: string } }}) => {
|
||||
// A structured message will have a cloud event content type
|
||||
expect(response.data["content-type"]).to.equal(DEFAULT_CE_CONTENT_TYPE);
|
||||
// Ensure other CE headers don't exist - just testing for ID
|
||||
expect(response.data[BINARY_HEADERS_03.ID]).to.equal(undefined);
|
||||
// The spec version would have been specified in the body
|
||||
expect(response.data.specversion).to.equal(SPEC_V03);
|
||||
expect(response.data.data.lunchBreak).to.equal(data.lunchBreak);
|
||||
}).catch(expect.fail);
|
||||
});
|
||||
|
||||
it("Sends to an alternate URL if specified", () => {
|
||||
nock(receiver)
|
||||
.post("/alternate")
|
||||
.reply(function (uri, requestBody: {}) {
|
||||
// return the request body and the headers so they can be
|
||||
// examined in the test
|
||||
if (typeof requestBody === "string") {
|
||||
requestBody = JSON.parse(requestBody);
|
||||
}
|
||||
const returnBody = { ...requestBody, ...this.req.headers };
|
||||
return [
|
||||
201,
|
||||
returnBody
|
||||
];
|
||||
});
|
||||
|
||||
emitter.send(event, { mode: "structured", url: `${receiver}alternate` })
|
||||
.then((response: { data: { specversion: string, data: { lunchBreak: string }, [k:string]: any }}) => {
|
||||
// A structured message will have a cloud event content type
|
||||
expect(response.data["content-type"]).to.equal(DEFAULT_CE_CONTENT_TYPE);
|
||||
// Ensure other CE headers don't exist - just testing for ID
|
||||
expect(response.data[BINARY_HEADERS_03.ID]).to.equal(undefined);
|
||||
// The spec version would have been specified in the body
|
||||
expect(response.data.specversion).to.equal(SPEC_V03);
|
||||
expect(response.data.data.lunchBreak).to.equal(data.lunchBreak);
|
||||
}).catch(expect.fail);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,436 +0,0 @@
|
|||
const expect = require("chai").expect;
|
||||
|
||||
const { CloudEvent } = require("../../../lib/cloudevent.js");
|
||||
const BinaryHTTPReceiver = require("../../../lib/bindings/http/receiver_binary.js");
|
||||
const ValidationError = require("../../../lib/bindings/http/validation/validation_error.js");
|
||||
const {
|
||||
BINARY_HEADERS_03,
|
||||
SPEC_V03,
|
||||
HEADER_CONTENT_TYPE
|
||||
} = require("../../../lib/bindings/http/constants.js");
|
||||
|
||||
const receiver = new BinaryHTTPReceiver(SPEC_V03);
|
||||
|
||||
describe("HTTP Transport Binding Binary Receiver for CloudEvents v0.3", () => {
|
||||
describe("Check", () => {
|
||||
it("Throw error when payload arg is null or undefined", () => {
|
||||
// setup
|
||||
const payload = null;
|
||||
const attributes = {};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.check.bind(receiver, payload, attributes))
|
||||
.to.throw(ValidationError, "payload is null or undefined");
|
||||
});
|
||||
|
||||
it("Throw error when attributes arg is null or undefined", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = null;
|
||||
|
||||
// act and assert
|
||||
expect(receiver.check.bind(receiver, payload, attributes))
|
||||
.to.throw(ValidationError, "attributes is null or undefined");
|
||||
});
|
||||
|
||||
it("Throw error when payload is not an object or string", () => {
|
||||
// setup
|
||||
const payload = 1.2;
|
||||
const attributes = {};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.check.bind(receiver, payload, attributes))
|
||||
.to.throw(ValidationError, "payload must be an object or a string");
|
||||
});
|
||||
|
||||
it("Throw error when headers has no 'ce-type'", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_03.SPEC_VERSION]: "specversion",
|
||||
[BINARY_HEADERS_03.SOURCE]: "source",
|
||||
[BINARY_HEADERS_03.ID]: "id",
|
||||
[HEADER_CONTENT_TYPE]: "application/json"
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.check.bind(receiver, payload, attributes))
|
||||
.to.throw(ValidationError, "header 'ce-type' not found");
|
||||
});
|
||||
|
||||
it("Throw error when headers has no 'ce-specversion'", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_03.TYPE]: "type",
|
||||
[BINARY_HEADERS_03.SOURCE]: "source",
|
||||
[BINARY_HEADERS_03.ID]: "id",
|
||||
[HEADER_CONTENT_TYPE]: "application/json"
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.check.bind(receiver, payload, attributes))
|
||||
.to.throw(ValidationError, "header 'ce-specversion' not found");
|
||||
});
|
||||
|
||||
it("Throw error when headers has no 'ce-source'", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_03.TYPE]: "type",
|
||||
[BINARY_HEADERS_03.SPEC_VERSION]: "specversion",
|
||||
[BINARY_HEADERS_03.ID]: "id",
|
||||
[HEADER_CONTENT_TYPE]: "application/json"
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.check.bind(receiver, payload, attributes))
|
||||
.to.throw(ValidationError, "header 'ce-source' not found");
|
||||
});
|
||||
|
||||
it("Throw error when headers has no 'ce-id'", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_03.TYPE]: "type",
|
||||
[BINARY_HEADERS_03.SPEC_VERSION]: "specversion",
|
||||
[BINARY_HEADERS_03.SOURCE]: "source",
|
||||
[HEADER_CONTENT_TYPE]: "application/json"
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.check.bind(receiver, payload, attributes))
|
||||
.to.throw(ValidationError, "header 'ce-id' not found");
|
||||
});
|
||||
|
||||
it("Throw error when spec is not 0.3", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_03.TYPE]: "type",
|
||||
[BINARY_HEADERS_03.SPEC_VERSION]: "0.2",
|
||||
[BINARY_HEADERS_03.SOURCE]: "source",
|
||||
[BINARY_HEADERS_03.ID]: "id",
|
||||
[HEADER_CONTENT_TYPE]: "application/json"
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.parse.bind(receiver, payload, attributes))
|
||||
.to.throw(ValidationError, "invalid spec version");
|
||||
});
|
||||
|
||||
it("Throw error when the content-type is invalid", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_03.TYPE]: "type",
|
||||
[BINARY_HEADERS_03.SPEC_VERSION]: "specversion",
|
||||
[BINARY_HEADERS_03.SOURCE]: "source",
|
||||
[BINARY_HEADERS_03.ID]: "id",
|
||||
[HEADER_CONTENT_TYPE]: "text/html"
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.check.bind(receiver, payload, attributes))
|
||||
.to.throw(ValidationError, "invalid content type");
|
||||
});
|
||||
|
||||
it("No error when all required headers are in place", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_03.TYPE]: "type",
|
||||
[BINARY_HEADERS_03.SPEC_VERSION]: SPEC_V03,
|
||||
[BINARY_HEADERS_03.SOURCE]: "source",
|
||||
[BINARY_HEADERS_03.ID]: "id",
|
||||
[HEADER_CONTENT_TYPE]: "application/json"
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.check.bind(receiver, payload, attributes))
|
||||
.to.not.throw();
|
||||
});
|
||||
|
||||
it("No error when content-type is unspecified", () => {
|
||||
const payload = {};
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_03.TYPE]: "type",
|
||||
[BINARY_HEADERS_03.SPEC_VERSION]: SPEC_V03,
|
||||
[BINARY_HEADERS_03.SOURCE]: "source",
|
||||
[BINARY_HEADERS_03.ID]: "id"
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.check.bind(receiver, payload, attributes))
|
||||
.to.not.throw();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Parse", () => {
|
||||
it("CloudEvent contains 'type'", () => {
|
||||
// setup
|
||||
const payload = {
|
||||
data: "dataString"
|
||||
};
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_03.TYPE]: "type",
|
||||
[BINARY_HEADERS_03.SPEC_VERSION]: SPEC_V03,
|
||||
[BINARY_HEADERS_03.SOURCE]: "source",
|
||||
[BINARY_HEADERS_03.ID]: "id",
|
||||
[BINARY_HEADERS_03.TIME]: "2019-06-16T11:42:00Z",
|
||||
[BINARY_HEADERS_03.SCHEMA_URL]: "http://schema.registry/v1",
|
||||
[HEADER_CONTENT_TYPE]: "application/json"
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.type).to.equal("type");
|
||||
});
|
||||
|
||||
it("CloudEvent contains 'specversion'", () => {
|
||||
// setup
|
||||
const payload = {
|
||||
data: "dataString"
|
||||
};
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_03.TYPE]: "type",
|
||||
[BINARY_HEADERS_03.SPEC_VERSION]: SPEC_V03,
|
||||
[BINARY_HEADERS_03.SOURCE]: "source",
|
||||
[BINARY_HEADERS_03.ID]: "id",
|
||||
[BINARY_HEADERS_03.TIME]: "2019-06-16T11:42:00Z",
|
||||
[BINARY_HEADERS_03.SCHEMA_URL]: "http://schema.registry/v1",
|
||||
[HEADER_CONTENT_TYPE]: "application/json"
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.specversion).to.equal(SPEC_V03);
|
||||
});
|
||||
|
||||
it("CloudEvent contains 'source'", () => {
|
||||
// setup
|
||||
const payload = {
|
||||
data: "dataString"
|
||||
};
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_03.TYPE]: "type",
|
||||
[BINARY_HEADERS_03.SPEC_VERSION]: SPEC_V03,
|
||||
[BINARY_HEADERS_03.SOURCE]: "/source",
|
||||
[BINARY_HEADERS_03.ID]: "id",
|
||||
[BINARY_HEADERS_03.TIME]: "2019-06-16T11:42:00Z",
|
||||
[BINARY_HEADERS_03.SCHEMA_URL]: "http://schema.registry/v1",
|
||||
[HEADER_CONTENT_TYPE]: "application/json"
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.source).to.equal("/source");
|
||||
});
|
||||
|
||||
it("CloudEvent contains 'id'", () => {
|
||||
// setup
|
||||
const payload = {
|
||||
data: "dataString"
|
||||
};
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_03.TYPE]: "type",
|
||||
[BINARY_HEADERS_03.SPEC_VERSION]: SPEC_V03,
|
||||
[BINARY_HEADERS_03.SOURCE]: "/source",
|
||||
[BINARY_HEADERS_03.ID]: "id",
|
||||
[BINARY_HEADERS_03.TIME]: "2019-06-16T11:42:00Z",
|
||||
[BINARY_HEADERS_03.SCHEMA_URL]: "http://schema.registry/v1",
|
||||
[HEADER_CONTENT_TYPE]: "application/json"
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.id).to.equal("id");
|
||||
});
|
||||
|
||||
it("CloudEvent contains 'time'", () => {
|
||||
// setup
|
||||
const payload = {
|
||||
data: "dataString"
|
||||
};
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_03.TYPE]: "type",
|
||||
[BINARY_HEADERS_03.SPEC_VERSION]: SPEC_V03,
|
||||
[BINARY_HEADERS_03.SOURCE]: "/source",
|
||||
[BINARY_HEADERS_03.ID]: "id",
|
||||
[BINARY_HEADERS_03.TIME]: "2019-06-16T11:42:00Z",
|
||||
[BINARY_HEADERS_03.SCHEMA_URL]: "http://schema.registry/v1",
|
||||
[HEADER_CONTENT_TYPE]: "application/json"
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.time).to.equal("2019-06-16T11:42:00.000Z");
|
||||
});
|
||||
|
||||
it("CloudEvent contains 'schemaurl'", () => {
|
||||
// setup
|
||||
const payload = {
|
||||
data: "dataString"
|
||||
};
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_03.TYPE]: "type",
|
||||
[BINARY_HEADERS_03.SPEC_VERSION]: SPEC_V03,
|
||||
[BINARY_HEADERS_03.SOURCE]: "/source",
|
||||
[BINARY_HEADERS_03.ID]: "id",
|
||||
[BINARY_HEADERS_03.TIME]: "2019-06-16T11:42:00Z",
|
||||
[BINARY_HEADERS_03.SCHEMA_URL]: "http://schema.registry/v1",
|
||||
[HEADER_CONTENT_TYPE]: "application/json"
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.schemaURL).to.equal("http://schema.registry/v1");
|
||||
});
|
||||
|
||||
it("CloudEvent contains 'datacontenttype' (application/json)", () => {
|
||||
// setup
|
||||
const payload = {
|
||||
data: "dataString"
|
||||
};
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_03.TYPE]: "type",
|
||||
[BINARY_HEADERS_03.SPEC_VERSION]: SPEC_V03,
|
||||
[BINARY_HEADERS_03.SOURCE]: "/source",
|
||||
[BINARY_HEADERS_03.ID]: "id",
|
||||
[BINARY_HEADERS_03.TIME]: "2019-06-16T11:42:00Z",
|
||||
[BINARY_HEADERS_03.SCHEMA_URL]: "http://schema.registry/v1",
|
||||
[HEADER_CONTENT_TYPE]: "application/json"
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.dataContentType).to.equal("application/json");
|
||||
});
|
||||
|
||||
it("CloudEvent contains 'datacontenttype' (application/octet-stream)",
|
||||
() => {
|
||||
// setup
|
||||
const payload = "The payload is binary data";
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_03.TYPE]: "type",
|
||||
[BINARY_HEADERS_03.SPEC_VERSION]: SPEC_V03,
|
||||
[BINARY_HEADERS_03.SOURCE]: "/source",
|
||||
[BINARY_HEADERS_03.ID]: "id",
|
||||
[BINARY_HEADERS_03.TIME]: "2019-06-16T11:42:00Z",
|
||||
[BINARY_HEADERS_03.SCHEMA_URL]: "http://schema.registry/v1",
|
||||
[HEADER_CONTENT_TYPE]: "application/octet-stream"
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.dataContentType).to.equal("application/octet-stream");
|
||||
});
|
||||
|
||||
it("CloudEvent contains 'data' (application/json)", () => {
|
||||
// setup
|
||||
const payload = {
|
||||
data: "dataString"
|
||||
};
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_03.TYPE]: "type",
|
||||
[BINARY_HEADERS_03.SPEC_VERSION]: SPEC_V03,
|
||||
[BINARY_HEADERS_03.SOURCE]: "/source",
|
||||
[BINARY_HEADERS_03.ID]: "id",
|
||||
[BINARY_HEADERS_03.TIME]: "2019-06-16T11:42:00Z",
|
||||
[BINARY_HEADERS_03.SCHEMA_URL]: "http://schema.registry/v1",
|
||||
[HEADER_CONTENT_TYPE]: "application/json"
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.data).to.deep.equal(payload);
|
||||
});
|
||||
|
||||
it("CloudEvent contains 'data' (application/octet-stream)", () => {
|
||||
// setup
|
||||
const payload = "The payload is binary data";
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_03.TYPE]: "type",
|
||||
[BINARY_HEADERS_03.SPEC_VERSION]: SPEC_V03,
|
||||
[BINARY_HEADERS_03.SOURCE]: "/source",
|
||||
[BINARY_HEADERS_03.ID]: "id",
|
||||
[BINARY_HEADERS_03.TIME]: "2019-06-16T11:42:00Z",
|
||||
[BINARY_HEADERS_03.SCHEMA_URL]: "http://schema.registry/v1",
|
||||
[HEADER_CONTENT_TYPE]: "application/octet-stream"
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.data).to.deep.equal(payload);
|
||||
});
|
||||
|
||||
it("No error when all attributes are in place", () => {
|
||||
// setup
|
||||
const payload = {
|
||||
data: "dataString"
|
||||
};
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_03.TYPE]: "type",
|
||||
[BINARY_HEADERS_03.SPEC_VERSION]: SPEC_V03,
|
||||
[BINARY_HEADERS_03.SOURCE]: "source",
|
||||
[BINARY_HEADERS_03.ID]: "id",
|
||||
[BINARY_HEADERS_03.TIME]: "2019-06-16T11:42:00Z",
|
||||
[BINARY_HEADERS_03.SCHEMA_URL]: "http://schema.registry/v1",
|
||||
[HEADER_CONTENT_TYPE]: "application/json"
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual).to.be.an.instanceof(CloudEvent);
|
||||
expect(actual).to.have.property("format");
|
||||
});
|
||||
|
||||
it("Should accept 'extension1'", () => {
|
||||
// setup
|
||||
const extension1 = "mycuston-ext1";
|
||||
const payload = {
|
||||
data: "dataString"
|
||||
};
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_03.TYPE]: "type",
|
||||
[BINARY_HEADERS_03.SPEC_VERSION]: SPEC_V03,
|
||||
[BINARY_HEADERS_03.SOURCE]: "source",
|
||||
[BINARY_HEADERS_03.ID]: "id",
|
||||
[BINARY_HEADERS_03.TIME]: "2019-06-16T11:42:00Z",
|
||||
[BINARY_HEADERS_03.SCHEMA_URL]: "http://schema.registry/v1",
|
||||
[HEADER_CONTENT_TYPE]: "application/json",
|
||||
[`${[BINARY_HEADERS_03.EXTENSIONS_PREFIX]}extension1`]: extension1
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
const actualExtensions = actual.getExtensions();
|
||||
|
||||
// assert
|
||||
expect(actualExtensions.extension1)
|
||||
.to.equal(extension1);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,462 +0,0 @@
|
|||
const expect = require("chai").expect;
|
||||
const { asBase64 } = require("../../../lib/bindings/http/validation/fun.js");
|
||||
const {
|
||||
BINARY_HEADERS_1,
|
||||
SPEC_V1,
|
||||
HEADER_CONTENT_TYPE
|
||||
} = require("../../../lib/bindings/http/constants.js");
|
||||
const ValidationError = require("../../../lib/bindings/http/validation/validation_error.js");
|
||||
const { CloudEvent } = require("../../../lib/cloudevent.js");
|
||||
const BinaryHTTPReceiver = require("../../../lib/bindings/http/receiver_binary.js");
|
||||
|
||||
const receiver = new BinaryHTTPReceiver(SPEC_V1);
|
||||
|
||||
describe("HTTP Transport Binding Binary Receiver for CloudEvents v1.0", () => {
|
||||
describe("Check", () => {
|
||||
it("Throw error when payload arg is null or undefined", () => {
|
||||
// setup
|
||||
const payload = null;
|
||||
const attributes = {};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.check.bind(receiver, payload, attributes))
|
||||
.to.throw(ValidationError, "payload is null or undefined");
|
||||
});
|
||||
|
||||
it("Throw error when attributes arg is null or undefined", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = null;
|
||||
|
||||
// act and assert
|
||||
expect(receiver.check.bind(receiver, payload, attributes))
|
||||
.to.throw(ValidationError, "attributes is null or undefined");
|
||||
});
|
||||
|
||||
it("Throw error when payload is not an object or string", () => {
|
||||
// setup
|
||||
const payload = 1.2;
|
||||
const attributes = {};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.check.bind(receiver, payload, attributes))
|
||||
.to.throw(ValidationError, "payload must be an object or a string");
|
||||
});
|
||||
|
||||
it("Throw error when headers has no 'ce-type'", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_1.SPEC_VERSION]: "specversion",
|
||||
[BINARY_HEADERS_1.SOURCE]: "source",
|
||||
[BINARY_HEADERS_1.ID]: "id",
|
||||
[HEADER_CONTENT_TYPE]: "application/json"
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.check.bind(receiver, payload, attributes))
|
||||
.to.throw(ValidationError, "header 'ce-type' not found");
|
||||
});
|
||||
|
||||
it("Throw error when headers has no 'ce-specversion'", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_1.TYPE]: "type",
|
||||
[BINARY_HEADERS_1.SOURCE]: "source",
|
||||
[BINARY_HEADERS_1.ID]: "id",
|
||||
[HEADER_CONTENT_TYPE]: "application/json"
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.check.bind(receiver, payload, attributes))
|
||||
.to.throw(ValidationError, "header 'ce-specversion' not found");
|
||||
});
|
||||
|
||||
it("Throw error when headers has no 'ce-source'", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_1.TYPE]: "type",
|
||||
[BINARY_HEADERS_1.SPEC_VERSION]: "specversion",
|
||||
[BINARY_HEADERS_1.ID]: "id",
|
||||
[HEADER_CONTENT_TYPE]: "application/json"
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.check.bind(receiver, payload, attributes))
|
||||
.to.throw(ValidationError, "header 'ce-source' not found");
|
||||
});
|
||||
|
||||
it("Throw error when headers has no 'ce-id'", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_1.TYPE]: "type",
|
||||
[BINARY_HEADERS_1.SPEC_VERSION]: "specversion",
|
||||
[BINARY_HEADERS_1.SOURCE]: "source",
|
||||
[HEADER_CONTENT_TYPE]: "application/json"
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.check.bind(receiver, payload, attributes))
|
||||
.to.throw(ValidationError, "header 'ce-id' not found");
|
||||
});
|
||||
|
||||
it("Throw error when spec is not 1.0", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_1.TYPE]: "type",
|
||||
[BINARY_HEADERS_1.SPEC_VERSION]: "0.2",
|
||||
[BINARY_HEADERS_1.SOURCE]: "source",
|
||||
[BINARY_HEADERS_1.ID]: "id",
|
||||
[HEADER_CONTENT_TYPE]: "application/json"
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.parse.bind(receiver, payload, attributes))
|
||||
.to.throw(ValidationError, "invalid spec version");
|
||||
});
|
||||
|
||||
it("Throw error when the content-type is invalid", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_1.TYPE]: "type",
|
||||
[BINARY_HEADERS_1.SPEC_VERSION]: "specversion",
|
||||
[BINARY_HEADERS_1.SOURCE]: "source",
|
||||
[BINARY_HEADERS_1.ID]: "id",
|
||||
[HEADER_CONTENT_TYPE]: "text/html"
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.check.bind(receiver, payload, attributes))
|
||||
.to.throw(ValidationError, "invalid content type");
|
||||
});
|
||||
|
||||
it("No error when content-type is unspecified", () => {
|
||||
const payload = {};
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_1.TYPE]: "type",
|
||||
[BINARY_HEADERS_1.SPEC_VERSION]: SPEC_V1,
|
||||
[BINARY_HEADERS_1.SOURCE]: "source",
|
||||
[BINARY_HEADERS_1.ID]: "id"
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.check.bind(receiver, payload, attributes))
|
||||
.to.not.throw();
|
||||
});
|
||||
|
||||
it("No error when all required headers are in place", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_1.TYPE]: "type",
|
||||
[BINARY_HEADERS_1.SPEC_VERSION]: SPEC_V1,
|
||||
[BINARY_HEADERS_1.SOURCE]: "source",
|
||||
[BINARY_HEADERS_1.ID]: "id",
|
||||
[HEADER_CONTENT_TYPE]: "application/json"
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.check.bind(receiver, payload, attributes))
|
||||
.to.not.throw();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Parse", () => {
|
||||
it("CloudEvent contains 'type'", () => {
|
||||
// setup
|
||||
const payload = {
|
||||
data: "dataString"
|
||||
};
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_1.TYPE]: "type",
|
||||
[BINARY_HEADERS_1.SPEC_VERSION]: SPEC_V1,
|
||||
[BINARY_HEADERS_1.SOURCE]: "source",
|
||||
[BINARY_HEADERS_1.ID]: "id",
|
||||
[BINARY_HEADERS_1.TIME]: "2019-06-16T11:42:00Z",
|
||||
[BINARY_HEADERS_1.DATA_SCHEMA]: "http://schema.registry/v1",
|
||||
[HEADER_CONTENT_TYPE]: "application/json"
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.type).to.equal("type");
|
||||
});
|
||||
|
||||
it("CloudEvent contains 'specversion'", () => {
|
||||
// setup
|
||||
const payload = {
|
||||
data: "dataString"
|
||||
};
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_1.TYPE]: "type",
|
||||
[BINARY_HEADERS_1.SPEC_VERSION]: SPEC_V1,
|
||||
[BINARY_HEADERS_1.SOURCE]: "source",
|
||||
[BINARY_HEADERS_1.ID]: "id",
|
||||
[BINARY_HEADERS_1.TIME]: "2019-06-16T11:42:00Z",
|
||||
[BINARY_HEADERS_1.DATA_SCHEMA]: "http://schema.registry/v1",
|
||||
[HEADER_CONTENT_TYPE]: "application/json"
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.specversion).to.equal(SPEC_V1);
|
||||
});
|
||||
|
||||
it("CloudEvent contains 'source'", () => {
|
||||
// setup
|
||||
const payload = {
|
||||
data: "dataString"
|
||||
};
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_1.TYPE]: "type",
|
||||
[BINARY_HEADERS_1.SPEC_VERSION]: SPEC_V1,
|
||||
[BINARY_HEADERS_1.SOURCE]: "/source",
|
||||
[BINARY_HEADERS_1.ID]: "id",
|
||||
[BINARY_HEADERS_1.TIME]: "2019-06-16T11:42:00Z",
|
||||
[BINARY_HEADERS_1.DATA_SCHEMA]: "http://schema.registry/v1",
|
||||
[HEADER_CONTENT_TYPE]: "application/json"
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.source).to.equal("/source");
|
||||
|
||||
});
|
||||
|
||||
it("CloudEvent contains 'id'", () => {
|
||||
// setup
|
||||
const payload = {
|
||||
data: "dataString"
|
||||
};
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_1.TYPE]: "type",
|
||||
[BINARY_HEADERS_1.SPEC_VERSION]: SPEC_V1,
|
||||
[BINARY_HEADERS_1.SOURCE]: "/source",
|
||||
[BINARY_HEADERS_1.ID]: "id",
|
||||
[BINARY_HEADERS_1.TIME]: "2019-06-16T11:42:00Z",
|
||||
[BINARY_HEADERS_1.DATA_SCHEMA]: "http://schema.registry/v1",
|
||||
[HEADER_CONTENT_TYPE]: "application/json"
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.id).to.equal("id");
|
||||
});
|
||||
|
||||
it("CloudEvent contains 'time'", () => {
|
||||
// setup
|
||||
const payload = {
|
||||
data: "dataString"
|
||||
};
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_1.TYPE]: "type",
|
||||
[BINARY_HEADERS_1.SPEC_VERSION]: SPEC_V1,
|
||||
[BINARY_HEADERS_1.SOURCE]: "/source",
|
||||
[BINARY_HEADERS_1.ID]: "id",
|
||||
[BINARY_HEADERS_1.TIME]: "2019-06-16T11:42:00Z",
|
||||
[BINARY_HEADERS_1.DATA_SCHEMA]: "http://schema.registry/v1",
|
||||
[HEADER_CONTENT_TYPE]: "application/json"
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.time).to.equal("2019-06-16T11:42:00.000Z");
|
||||
});
|
||||
|
||||
it("CloudEvent contains 'dataschema'", () => {
|
||||
// setup
|
||||
const payload = {
|
||||
data: "dataString"
|
||||
};
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_1.TYPE]: "type",
|
||||
[BINARY_HEADERS_1.SPEC_VERSION]: SPEC_V1,
|
||||
[BINARY_HEADERS_1.SOURCE]: "/source",
|
||||
[BINARY_HEADERS_1.ID]: "id",
|
||||
[BINARY_HEADERS_1.TIME]: "2019-06-16T11:42:00Z",
|
||||
[BINARY_HEADERS_1.DATA_SCHEMA]: "http://schema.registry/v1",
|
||||
[HEADER_CONTENT_TYPE]: "application/json"
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.dataSchema).to.equal("http://schema.registry/v1");
|
||||
});
|
||||
|
||||
it("CloudEvent contains 'contenttype' (application/json)", () => {
|
||||
// setup
|
||||
const payload = {
|
||||
data: "dataString"
|
||||
};
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_1.TYPE]: "type",
|
||||
[BINARY_HEADERS_1.SPEC_VERSION]: SPEC_V1,
|
||||
[BINARY_HEADERS_1.SOURCE]: "/source",
|
||||
[BINARY_HEADERS_1.ID]: "id",
|
||||
[BINARY_HEADERS_1.TIME]: "2019-06-16T11:42:00Z",
|
||||
[BINARY_HEADERS_1.DATA_SCHEMA]: "http://schema.registry/v1",
|
||||
[HEADER_CONTENT_TYPE]: "application/json"
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.dataContentType).to.equal("application/json");
|
||||
});
|
||||
|
||||
it("CloudEvent contains 'contenttype' (application/octet-stream)", () => {
|
||||
// setup
|
||||
const payload = "The payload is binary data";
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_1.TYPE]: "type",
|
||||
[BINARY_HEADERS_1.SPEC_VERSION]: SPEC_V1,
|
||||
[BINARY_HEADERS_1.SOURCE]: "/source",
|
||||
[BINARY_HEADERS_1.ID]: "id",
|
||||
[BINARY_HEADERS_1.TIME]: "2019-06-16T11:42:00Z",
|
||||
[BINARY_HEADERS_1.DATA_SCHEMA]: "http://schema.registry/v1",
|
||||
[HEADER_CONTENT_TYPE]: "application/octet-stream"
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.dataContentType).to.equal("application/octet-stream");
|
||||
});
|
||||
|
||||
it("CloudEvent contains 'data' (application/json)", () => {
|
||||
// setup
|
||||
const payload = {
|
||||
data: "dataString"
|
||||
};
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_1.TYPE]: "type",
|
||||
[BINARY_HEADERS_1.SPEC_VERSION]: SPEC_V1,
|
||||
[BINARY_HEADERS_1.SOURCE]: "/source",
|
||||
[BINARY_HEADERS_1.ID]: "id",
|
||||
[BINARY_HEADERS_1.TIME]: "2019-06-16T11:42:00Z",
|
||||
[BINARY_HEADERS_1.DATA_SCHEMA]: "http://schema.registry/v1",
|
||||
[HEADER_CONTENT_TYPE]: "application/json"
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.data).to.deep.equal(payload);
|
||||
});
|
||||
|
||||
it("CloudEvent contains 'data' (application/octet-stream)", () => {
|
||||
// setup
|
||||
const payload = "The payload is binary data";
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_1.TYPE]: "type",
|
||||
[BINARY_HEADERS_1.SPEC_VERSION]: SPEC_V1,
|
||||
[BINARY_HEADERS_1.SOURCE]: "/source",
|
||||
[BINARY_HEADERS_1.ID]: "id",
|
||||
[BINARY_HEADERS_1.TIME]: "2019-06-16T11:42:00Z",
|
||||
[BINARY_HEADERS_1.DATA_SCHEMA]: "http://schema.registry/v1",
|
||||
[HEADER_CONTENT_TYPE]: "application/octet-stream"
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.data).to.deep.equal(payload);
|
||||
});
|
||||
|
||||
it("The content of 'data' is base64 for binary", () => {
|
||||
// setup
|
||||
const expected = {
|
||||
data: "dataString"
|
||||
};
|
||||
|
||||
const bindata = Uint32Array.from(JSON.stringify(expected), (c) => c.codePointAt(0));
|
||||
const payload = asBase64(bindata);
|
||||
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_1.TYPE]: "type",
|
||||
[BINARY_HEADERS_1.SPEC_VERSION]: SPEC_V1,
|
||||
[BINARY_HEADERS_1.SOURCE]: "/source",
|
||||
[BINARY_HEADERS_1.ID]: "id",
|
||||
[BINARY_HEADERS_1.TIME]: "2019-06-16T11:42:00Z",
|
||||
[BINARY_HEADERS_1.DATA_SCHEMA]: "http://schema.registry/v1",
|
||||
[HEADER_CONTENT_TYPE]: "application/json"
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.data).to.deep.equal(expected);
|
||||
});
|
||||
|
||||
it("No error when all attributes are in place", () => {
|
||||
// setup
|
||||
const payload = {
|
||||
data: "dataString"
|
||||
};
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_1.TYPE]: "type",
|
||||
[BINARY_HEADERS_1.SPEC_VERSION]: SPEC_V1,
|
||||
[BINARY_HEADERS_1.SOURCE]: "source",
|
||||
[BINARY_HEADERS_1.ID]: "id",
|
||||
[BINARY_HEADERS_1.TIME]: "2019-06-16T11:42:00Z",
|
||||
[BINARY_HEADERS_1.DATA_SCHEMA]: "http://schema.registry/v1",
|
||||
[HEADER_CONTENT_TYPE]: "application/json"
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual).to.be.an.instanceof(CloudEvent);
|
||||
|
||||
expect(actual).to.have.property("format");
|
||||
});
|
||||
|
||||
it("Should accept 'extension1'", () => {
|
||||
// setup
|
||||
const extension1 = "mycustom-ext1";
|
||||
const payload = {
|
||||
data: "dataString"
|
||||
};
|
||||
const attributes = {
|
||||
[BINARY_HEADERS_1.TYPE]: "type",
|
||||
[BINARY_HEADERS_1.SPEC_VERSION]: SPEC_V1,
|
||||
[BINARY_HEADERS_1.SOURCE]: "source",
|
||||
[BINARY_HEADERS_1.ID]: "id",
|
||||
[BINARY_HEADERS_1.TIME]: "2019-06-16T11:42:00Z",
|
||||
[BINARY_HEADERS_1.DATA_SCHEMA]: "http://schema.registry/v1",
|
||||
[HEADER_CONTENT_TYPE]: "application/json",
|
||||
[`${[BINARY_HEADERS_1.EXTENSIONS_PREFIX]}extension1`]: extension1
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
const actualExtensions = actual.getExtensions();
|
||||
|
||||
// assert
|
||||
expect(actualExtensions.extension1).to.equal(extension1);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,213 +0,0 @@
|
|||
const expect = require("chai").expect;
|
||||
const ValidationError = require("../../../lib/bindings/http/validation/validation_error.js");
|
||||
const HTTPStructuredReceiver = require("../../../lib/bindings/http/receiver_structured.js");
|
||||
const { CloudEvent } = require("../../../index.js");
|
||||
const { SPEC_V03 } = require("../../../lib/bindings/http/constants.js");
|
||||
|
||||
const receiver = new HTTPStructuredReceiver(SPEC_V03);
|
||||
|
||||
const type = "com.github.pull.create";
|
||||
const source = "urn:event:from:myapi/resourse/123";
|
||||
const time = new Date();
|
||||
const schemaURL = "http://cloudevents.io/schema.json";
|
||||
|
||||
const ceContentType = "application/json";
|
||||
|
||||
const data = {
|
||||
foo: "bar"
|
||||
};
|
||||
|
||||
const ext1Name = "extension1";
|
||||
const ext1Value = "foobar";
|
||||
const ext2Name = "extension2";
|
||||
const ext2Value = "acme";
|
||||
|
||||
describe("HTTP Transport Binding Structured Receiver CloudEvents v0.3", () => {
|
||||
describe("Check", () => {
|
||||
it("Throw error when payload arg is null or undefined", () => {
|
||||
// setup
|
||||
const payload = null;
|
||||
const attributes = {};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.check.bind(receiver, payload, attributes))
|
||||
.to.throw(ValidationError, "payload is null or undefined");
|
||||
});
|
||||
|
||||
it("Throw error when attributes arg is null or undefined", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = null;
|
||||
|
||||
// act and assert
|
||||
expect(receiver.check.bind(receiver, payload, attributes))
|
||||
.to.throw(ValidationError, "attributes is null or undefined");
|
||||
});
|
||||
|
||||
it("Throw error when payload is not an object or string", () => {
|
||||
// setup
|
||||
const payload = 1.0;
|
||||
const attributes = {};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.check.bind(receiver, payload, attributes))
|
||||
.to.throw(ValidationError, "payload must be an object or a string");
|
||||
});
|
||||
|
||||
it("Throw error when the content-type is invalid", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = {
|
||||
"Content-Type": "text/html"
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.check.bind(receiver, payload, attributes))
|
||||
.to.throw(ValidationError, "invalid content type");
|
||||
});
|
||||
|
||||
it("Throw error data content encoding is base64, but 'data' is not",
|
||||
() => {
|
||||
// setup
|
||||
const event = new CloudEvent({
|
||||
specversion: SPEC_V03,
|
||||
type,
|
||||
source,
|
||||
time,
|
||||
dataContentType: "text/plain",
|
||||
dataContentEncoding: "base64",
|
||||
schemaURL,
|
||||
data: "No base 64 value"
|
||||
});
|
||||
event.addExtension(ext1Name, ext1Value);
|
||||
event.addExtension(ext2Name, ext2Value);
|
||||
const payload = event.toString();
|
||||
|
||||
const attributes = {
|
||||
"Content-Type": "application/cloudevents+json"
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.parse.bind(receiver, payload, attributes))
|
||||
.to.throw(ValidationError, "invalid payload");
|
||||
});
|
||||
|
||||
it("No error when all required stuff are in place", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = {
|
||||
"Content-Type": "application/cloudevents+json"
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.check.bind(receiver, payload, attributes))
|
||||
.to.not.throw();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Parse", () => {
|
||||
it("Throw error when the event does not follow the spec", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = {
|
||||
"Content-Type": "application/cloudevents+json"
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.check.bind(receiver, payload, attributes))
|
||||
.to.not.throw();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Parse", () => {
|
||||
it("Throw error when the event does not follow the spec", () => {
|
||||
const payload = {
|
||||
type,
|
||||
source,
|
||||
time,
|
||||
schemaURL,
|
||||
data
|
||||
};
|
||||
|
||||
const headers = {
|
||||
"Content-Type": "application/cloudevents+xml"
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.parse.bind(receiver, payload, headers))
|
||||
.to.throw(ValidationError, "invalid content type");
|
||||
});
|
||||
|
||||
it("Should accept event that follows the spec", () => {
|
||||
// setup
|
||||
const id = "id-x0dk";
|
||||
const payload = {
|
||||
id,
|
||||
type,
|
||||
source,
|
||||
time,
|
||||
schemaURL,
|
||||
dataContentType: ceContentType,
|
||||
data
|
||||
};
|
||||
const headers = {
|
||||
"content-type": "application/cloudevents+json"
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, headers);
|
||||
|
||||
// assert
|
||||
expect(actual).to.be.an.instanceof(CloudEvent);
|
||||
|
||||
expect(actual).to.have.property("format");
|
||||
|
||||
expect(actual.id).to.equal(id);
|
||||
});
|
||||
|
||||
it("Should accept 'extension1'", () => {
|
||||
// setup
|
||||
const extension1 = "mycuston-ext1";
|
||||
const payload = {
|
||||
type,
|
||||
source,
|
||||
time,
|
||||
schemaURL,
|
||||
data,
|
||||
dataContentType: ceContentType,
|
||||
"extension1": extension1
|
||||
};
|
||||
const headers = {
|
||||
"content-type": "application/cloudevents+json"
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, headers);
|
||||
const actualExtensions = actual.getExtensions();
|
||||
|
||||
// assert
|
||||
expect(actualExtensions.extension1).to.equal(extension1);
|
||||
});
|
||||
|
||||
it("Should parse 'data' stringfied json to json object", () => {
|
||||
const payload = new CloudEvent({
|
||||
specversion: SPEC_V03,
|
||||
type,
|
||||
source,
|
||||
time,
|
||||
schemaURL,
|
||||
dataContentType: ceContentType,
|
||||
data: JSON.stringify(data)
|
||||
}).toString();
|
||||
const headers = {
|
||||
"content-type": "application/cloudevents+json"
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, headers);
|
||||
|
||||
// assert
|
||||
expect(actual.data).to.deep.equal(data);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,196 +0,0 @@
|
|||
const expect = require("chai").expect;
|
||||
const { Spec } = require("../../../lib/bindings/http/v1/index.js");
|
||||
const { CloudEvent } = require("../../../index.js");
|
||||
const { asBase64 } = require("../../../lib/bindings/http/validation/fun.js");
|
||||
const { SPEC_V1 } = require("../../../lib/bindings/http/constants.js");
|
||||
const ValidationError = require("../../../lib/bindings/http/validation/validation_error.js");
|
||||
const HTTPStructuredReceiver = require("../../../lib/bindings/http/receiver_structured.js");
|
||||
|
||||
const receiver = new HTTPStructuredReceiver(SPEC_V1);
|
||||
|
||||
const type = "com.github.pull.create";
|
||||
const source = "urn:event:from:myapi/resource/123";
|
||||
const time = new Date();
|
||||
const dataSchema = "http://cloudevents.io/schema.json";
|
||||
|
||||
const ceContentType = "application/json";
|
||||
|
||||
const data = {
|
||||
foo: "bar"
|
||||
};
|
||||
|
||||
describe("HTTP Transport Binding Structured Receiver for CloudEvents v1.0",
|
||||
() => {
|
||||
describe("Check", () => {
|
||||
it("Throw error when payload arg is null or undefined", () => {
|
||||
// setup
|
||||
const payload = null;
|
||||
const attributes = {};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.check.bind(receiver, payload, attributes))
|
||||
.to.throw(ValidationError, "payload is null or undefined");
|
||||
});
|
||||
|
||||
it("Throw error when attributes arg is null or undefined", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = null;
|
||||
|
||||
// act and assert
|
||||
expect(receiver.check.bind(receiver, payload, attributes))
|
||||
.to.throw(ValidationError, "attributes is null or undefined");
|
||||
});
|
||||
|
||||
it("Throw error when payload is not an object or string", () => {
|
||||
// setup
|
||||
const payload = 1.0;
|
||||
const attributes = {};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.check.bind(receiver, payload, attributes))
|
||||
.to.throw(ValidationError, "payload must be an object or a string");
|
||||
});
|
||||
|
||||
it("Throw error when the content-type is invalid", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = {
|
||||
"Content-Type": "text/html"
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.check.bind(receiver, payload, attributes))
|
||||
.to.throw(ValidationError, "invalid content type");
|
||||
});
|
||||
|
||||
it("No error when all required stuff are in place", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = {
|
||||
"Content-Type": "application/cloudevents+json"
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.check.bind(receiver, payload, attributes))
|
||||
.to.not.throw();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Parse", () => {
|
||||
it("Throw error when the event does not follow the spec", () => {
|
||||
// setup
|
||||
const payload = new CloudEvent({
|
||||
type,
|
||||
source,
|
||||
time,
|
||||
data
|
||||
}).toString();
|
||||
|
||||
const headers = {
|
||||
"Content-Type": "application/cloudevents+xml"
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.parse.bind(receiver, payload, headers))
|
||||
.to.throw(ValidationError, "invalid content type");
|
||||
});
|
||||
|
||||
it("Should accept event that follows the spec", () => {
|
||||
// setup
|
||||
const id = "id-x0dk";
|
||||
const payload = new CloudEvent({
|
||||
id,
|
||||
type,
|
||||
source,
|
||||
time,
|
||||
data,
|
||||
dataSchema,
|
||||
dataContentType: ceContentType,
|
||||
}).toString();
|
||||
const headers = {
|
||||
"content-type": "application/cloudevents+json"
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, headers);
|
||||
|
||||
// assert
|
||||
expect(actual).to.be.an.instanceof(CloudEvent);
|
||||
|
||||
expect(actual).to.have.property("format");
|
||||
|
||||
expect(actual.id).to.equal(id);
|
||||
});
|
||||
|
||||
it("Should accept 'extension1'", () => {
|
||||
// setup
|
||||
const extension1 = "mycustom-ext1";
|
||||
const event = new CloudEvent({
|
||||
type,
|
||||
source,
|
||||
time,
|
||||
data,
|
||||
dataSchema,
|
||||
dataContentType: ceContentType
|
||||
});
|
||||
event.addExtension("extension1", extension1);
|
||||
const payload = event.toString();
|
||||
|
||||
const headers = {
|
||||
"content-type": "application/cloudevents+json"
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, headers);
|
||||
const actualExtensions = actual.getExtensions();
|
||||
|
||||
// assert
|
||||
expect(actualExtensions.extension1).to.equal(extension1);
|
||||
});
|
||||
|
||||
it("Should parse 'data' stringified json to json object", () => {
|
||||
// setup
|
||||
const payload = new CloudEvent({
|
||||
type,
|
||||
source,
|
||||
time,
|
||||
dataSchema,
|
||||
data: JSON.stringify(data),
|
||||
dataContentType: ceContentType
|
||||
}).toString();
|
||||
|
||||
const headers = {
|
||||
"content-type": "application/cloudevents+json"
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, headers);
|
||||
|
||||
// assert
|
||||
expect(actual.data).to.deep.equal(data);
|
||||
});
|
||||
|
||||
it("Should maps 'data_base64' to 'data' attribute", () => {
|
||||
// setup
|
||||
const bindata = Uint32Array.from(JSON.stringify(data), (c) => c.codePointAt(0));
|
||||
const expected = asBase64(bindata);
|
||||
const payload = new CloudEvent({
|
||||
type,
|
||||
source,
|
||||
data: bindata,
|
||||
dataContentType: ceContentType
|
||||
}).format();
|
||||
|
||||
const headers = {
|
||||
"content-type": "application/cloudevents+json"
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(JSON.stringify(payload), headers);
|
||||
|
||||
// assert
|
||||
expect(actual.data).to.equal(expected);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,174 @@
|
|||
import { expect } from "chai";
|
||||
import { CloudEvent, Version } from "../src";
|
||||
import { CloudEventV03 } from "../src/event/v03";
|
||||
import { CloudEventV1 } from "../src/event/v1";
|
||||
|
||||
const type = "org.cncf.cloudevents.example";
|
||||
const source = "http://unit.test";
|
||||
const id = "b46cf653-d48a-4b90-8dfa-355c01061361";
|
||||
|
||||
const fixture: CloudEventV1 = {
|
||||
id,
|
||||
specversion: Version.V1,
|
||||
source,
|
||||
type,
|
||||
};
|
||||
|
||||
describe("A CloudEvent", () => {
|
||||
it("Can be constructed with a typed Message", () => {
|
||||
const ce = new CloudEvent(fixture);
|
||||
expect(ce.type).to.equal(type);
|
||||
expect(ce.source).to.equal(source);
|
||||
});
|
||||
|
||||
it("serializes as JSON with toString()", () => {
|
||||
const ce = new CloudEvent(fixture);
|
||||
expect(ce.toString()).to.deep.equal(JSON.stringify(ce));
|
||||
});
|
||||
});
|
||||
|
||||
describe("A 1.0 CloudEvent", () => {
|
||||
it("has retreivable source and type attributes", () => {
|
||||
const ce = new CloudEvent(fixture);
|
||||
expect(ce.source).to.equal("http://unit.test");
|
||||
expect(ce.type).to.equal("org.cncf.cloudevents.example");
|
||||
});
|
||||
|
||||
it("defaults to specversion 1.0", () => {
|
||||
const ce = new CloudEvent({ source, type });
|
||||
expect(ce.specversion).to.equal("1.0");
|
||||
});
|
||||
|
||||
it("generates an ID if one is not provided in the constructor", () => {
|
||||
const ce = new CloudEvent({ source, type });
|
||||
expect(ce.id).to.not.be.empty;
|
||||
});
|
||||
|
||||
it("can be constructed with an ID", () => {
|
||||
const ce = new CloudEvent({ id: "1234", specversion: Version.V1, source, type });
|
||||
expect(ce.id).to.equal("1234");
|
||||
});
|
||||
|
||||
it("generates a timestamp by default", () => {
|
||||
const ce = new CloudEvent(fixture);
|
||||
expect(ce.time).to.not.be.empty;
|
||||
});
|
||||
|
||||
it("can be constructed with a timestamp", () => {
|
||||
const time = new Date().toISOString();
|
||||
const ce = new CloudEvent({ time, ...fixture });
|
||||
expect(ce.time).to.equal(time);
|
||||
});
|
||||
|
||||
it("can be constructed with a datacontenttype", () => {
|
||||
const ce = new CloudEvent({ datacontenttype: "application/json", ...fixture });
|
||||
expect(ce.datacontenttype).to.equal("application/json");
|
||||
});
|
||||
|
||||
it("can be constructed with a dataschema", () => {
|
||||
const ce = new CloudEvent({ dataschema: "http://my.schema", ...fixture });
|
||||
expect(ce.dataschema).to.equal("http://my.schema");
|
||||
});
|
||||
|
||||
it("can be constructed with a subject", () => {
|
||||
const ce = new CloudEvent({ subject: "science", ...fixture });
|
||||
expect(ce.subject).to.equal("science");
|
||||
});
|
||||
|
||||
// Handle deprecated attribute - should this really throw?
|
||||
it("throws a TypeError when constructed with a schemaurl", () => {
|
||||
expect(() => {
|
||||
new CloudEvent({ schemaurl: "http://throw.com", ...fixture });
|
||||
}).to.throw(TypeError, "cannot set schemaurl on version 1.0 event");
|
||||
});
|
||||
|
||||
it("can be constructed with data", () => {
|
||||
const ce = new CloudEvent({
|
||||
...fixture,
|
||||
data: { lunch: "tacos" },
|
||||
});
|
||||
expect(ce.data).to.deep.equal({ lunch: "tacos" });
|
||||
});
|
||||
|
||||
it("can be constructed with extensions", () => {
|
||||
const extensions = {
|
||||
"extension-key": "extension-value",
|
||||
};
|
||||
const ce = new CloudEvent({
|
||||
...extensions,
|
||||
...fixture,
|
||||
});
|
||||
expect(ce["extension-key"]).to.equal(extensions["extension-key"]);
|
||||
});
|
||||
|
||||
it("throws ValidationError if the CloudEvent does not conform to the schema");
|
||||
it("returns a JSON string even if format is invalid");
|
||||
it("correctly formats a CloudEvent as JSON");
|
||||
});
|
||||
|
||||
describe("A 0.3 CloudEvent", () => {
|
||||
const v03fixture: CloudEventV03 = { ...fixture };
|
||||
v03fixture.specversion = Version.V03;
|
||||
|
||||
it("has retreivable source and type attributes", () => {
|
||||
const ce = new CloudEvent(v03fixture);
|
||||
expect(ce.source).to.equal("http://unit.test");
|
||||
expect(ce.type).to.equal("org.cncf.cloudevents.example");
|
||||
});
|
||||
|
||||
it("generates an ID if one is not provided in the constructor", () => {
|
||||
const ce = new CloudEvent({ source, type, specversion: Version.V03 });
|
||||
expect(ce.id).to.not.be.empty;
|
||||
expect(ce.specversion).to.equal(Version.V03);
|
||||
});
|
||||
|
||||
it("generates a timestamp by default", () => {
|
||||
const ce = new CloudEvent(v03fixture);
|
||||
expect(ce.time).to.not.be.empty;
|
||||
});
|
||||
|
||||
it("can be constructed with a timestamp", () => {
|
||||
const time = new Date();
|
||||
const ce = new CloudEvent({ time, ...v03fixture });
|
||||
expect(ce.time).to.equal(time.toISOString());
|
||||
});
|
||||
|
||||
it("can be constructed with a datacontenttype", () => {
|
||||
const ce = new CloudEvent({ datacontenttype: "application/json", ...v03fixture });
|
||||
expect(ce.datacontenttype).to.equal("application/json");
|
||||
});
|
||||
|
||||
it("can be constructed with a datacontentencoding", () => {
|
||||
const ce = new CloudEvent({ datacontentencoding: "Base64", ...v03fixture });
|
||||
expect(ce.datacontentencoding).to.equal("Base64");
|
||||
});
|
||||
|
||||
it("can be constructed with a schemaurl", () => {
|
||||
const ce = new CloudEvent({ schemaurl: "http://my.schema", ...v03fixture });
|
||||
expect(ce.schemaurl).to.equal("http://my.schema");
|
||||
});
|
||||
|
||||
it("can be constructed with a subject", () => {
|
||||
const ce = new CloudEvent({ subject: "science", ...v03fixture });
|
||||
expect(ce.subject).to.equal("science");
|
||||
});
|
||||
|
||||
// Handle 1.0 attribute - should this really throw?
|
||||
it("throws a TypeError when constructed with a dataschema", () => {
|
||||
expect(() => {
|
||||
new CloudEvent({ dataschema: "http://throw.com", ...v03fixture });
|
||||
}).to.throw(TypeError, "cannot set dataschema on version 0.3 event");
|
||||
});
|
||||
|
||||
it("can be constructed with data", () => {
|
||||
const ce = new CloudEvent({
|
||||
...v03fixture,
|
||||
data: { lunch: "tacos" },
|
||||
});
|
||||
expect(ce.data).to.deep.equal({ lunch: "tacos" });
|
||||
});
|
||||
|
||||
it("throws ValidationError if the CloudEvent does not conform to the schema");
|
||||
it("returns a JSON string even if format is invalid");
|
||||
it("correctly formats a CloudEvent as JSON");
|
||||
});
|
|
@ -1,191 +0,0 @@
|
|||
const expect = require("chai").expect;
|
||||
|
||||
const {
|
||||
HEADERS,
|
||||
CHARSET_DEFAULT,
|
||||
BINARY,
|
||||
STRUCTURED,
|
||||
SPEC_V03,
|
||||
SPEC_V1,
|
||||
DEFAULT_SPEC_VERSION_HEADER,
|
||||
ENCODING_BASE64,
|
||||
DATA_ATTRIBUTE,
|
||||
MIME_JSON,
|
||||
MIME_OCTET_STREAM,
|
||||
MIME_CE,
|
||||
MIME_CE_JSON,
|
||||
HEADER_CONTENT_TYPE,
|
||||
DEFAULT_CONTENT_TYPE,
|
||||
DEFAULT_CE_CONTENT_TYPE,
|
||||
BINARY_HEADERS_03,
|
||||
STRUCTURED_ATTRS_03,
|
||||
BINARY_HEADERS_1,
|
||||
STRUCTURED_ATTRS_1
|
||||
} = require("../").Constants;
|
||||
|
||||
describe("Constants exposed by top level exports", () => {
|
||||
it("Exports a HEADERS constant", () => {
|
||||
expect(HEADERS).to.equal("headers");
|
||||
});
|
||||
it("Exports a CHARSET_DEFAULT constant", () => {
|
||||
expect(CHARSET_DEFAULT).to.equal("utf-8");
|
||||
});
|
||||
it("Exports a BINARY constant", () => {
|
||||
expect(BINARY).to.equal("binary");
|
||||
});
|
||||
it("Exports a STRUCTURED constant", () => {
|
||||
expect(STRUCTURED).to.equal("structured");
|
||||
});
|
||||
it("Exports a SPEC_V03 constant", () => {
|
||||
expect(SPEC_V03).to.equal("0.3");
|
||||
});
|
||||
it("Exports a SPEC_V1 constant", () => {
|
||||
expect(SPEC_V1).to.equal("1.0");
|
||||
});
|
||||
it("Exports a DEFAULT_SPEC_VERSION_HEADER constant", () => {
|
||||
expect(DEFAULT_SPEC_VERSION_HEADER).to.equal("ce-specversion");
|
||||
});
|
||||
it("Exports an ENCODING_BASE64 constant", () => {
|
||||
expect(ENCODING_BASE64).to.equal("base64");
|
||||
});
|
||||
it("Exports a DATA_ATTRIBUTE constant", () => {
|
||||
expect(DATA_ATTRIBUTE).to.equal("data");
|
||||
});
|
||||
it("Exports a MIME_JSON constant", () => {
|
||||
expect(MIME_JSON).to.equal("application/json");
|
||||
});
|
||||
it("Exports a MIME_OCTET_STREAM constant", () => {
|
||||
expect(MIME_OCTET_STREAM).to.equal("application/octet-stream");
|
||||
});
|
||||
it("Exports a MIME_CE constant", () => {
|
||||
expect(MIME_CE).to.equal("application/cloudevents");
|
||||
});
|
||||
it("Exports a MIME_CE_JSON constant", () => {
|
||||
expect(MIME_CE_JSON).to.equal("application/cloudevents+json");
|
||||
});
|
||||
it("Exports a HEADER_CONTENT_TYPE constant", () => {
|
||||
expect(HEADER_CONTENT_TYPE).to.equal("content-type");
|
||||
});
|
||||
it("Exports a DEFAULT_CONTENT_TYPE constant", () => {
|
||||
expect(DEFAULT_CONTENT_TYPE).to.equal(`${MIME_JSON}; charset=${CHARSET_DEFAULT}`);
|
||||
});
|
||||
it("Exports a DEFAULT_CE_CONTENT_TYPE constant", () => {
|
||||
expect(DEFAULT_CE_CONTENT_TYPE).to.equal(`${MIME_CE_JSON}; charset=${CHARSET_DEFAULT}`);
|
||||
});
|
||||
describe("V0.3 binary headers constants", () => {
|
||||
it("Provides a TYPE header", () => {
|
||||
expect(BINARY_HEADERS_03.TYPE).to.equal("ce-type");
|
||||
});
|
||||
it("Provides a SPEC_VERSION header", () => {
|
||||
expect(BINARY_HEADERS_03.SPEC_VERSION).to.equal("ce-specversion");
|
||||
});
|
||||
it("Provides a SOURCE header", () => {
|
||||
expect(BINARY_HEADERS_03.SOURCE).to.equal("ce-source");
|
||||
});
|
||||
it("Provides an ID header", () => {
|
||||
expect(BINARY_HEADERS_03.ID).to.equal("ce-id");
|
||||
});
|
||||
it("Provides a TIME header", () => {
|
||||
expect(BINARY_HEADERS_03.TIME).to.equal("ce-time");
|
||||
});
|
||||
it("Provides a SCHEMA_URL header", () => {
|
||||
expect(BINARY_HEADERS_03.SCHEMA_URL).to.equal("ce-schemaurl");
|
||||
});
|
||||
it("Provides a CONTENT_ENCODING header", () => {
|
||||
expect(BINARY_HEADERS_03.CONTENT_ENCODING).to.equal("ce-datacontentencoding");
|
||||
});
|
||||
it("Provides a SUBJECT header", () => {
|
||||
expect(BINARY_HEADERS_03.SUBJECT).to.equal("ce-subject");
|
||||
});
|
||||
it("Provides an EXTENSIONS_PREFIX constant", () => {
|
||||
expect(BINARY_HEADERS_03.EXTENSIONS_PREFIX).to.equal("ce-");
|
||||
});
|
||||
});
|
||||
describe("V0.3 structured attributes constants", () => {
|
||||
it("Provides a TYPE attribute", () => {
|
||||
expect(STRUCTURED_ATTRS_03.TYPE).to.equal("type");
|
||||
});
|
||||
it("Provides a SPEC_VERSION attribute", () => {
|
||||
expect(STRUCTURED_ATTRS_03.SPEC_VERSION).to.equal("specversion");
|
||||
});
|
||||
it("Provides a SOURCE attribute", () => {
|
||||
expect(STRUCTURED_ATTRS_03.SOURCE).to.equal("source");
|
||||
});
|
||||
it("Provides an ID attribute", () => {
|
||||
expect(STRUCTURED_ATTRS_03.ID).to.equal("id");
|
||||
});
|
||||
it("Provides a TIME attribute", () => {
|
||||
expect(STRUCTURED_ATTRS_03.TIME).to.equal("time");
|
||||
});
|
||||
it("Provides a SCHEMA_URL attribute", () => {
|
||||
expect(STRUCTURED_ATTRS_03.SCHEMA_URL).to.equal("schemaurl");
|
||||
});
|
||||
it("Provides a CONTENT_ENCODING attribute", () => {
|
||||
expect(STRUCTURED_ATTRS_03.CONTENT_ENCODING).to.equal("datacontentencoding");
|
||||
});
|
||||
it("Provides a SUBJECT attribute", () => {
|
||||
expect(STRUCTURED_ATTRS_03.SUBJECT).to.equal("subject");
|
||||
});
|
||||
it("Provides a DATA attribute", () => {
|
||||
expect(STRUCTURED_ATTRS_03.DATA).to.equal("data");
|
||||
});
|
||||
});
|
||||
describe("V01 binary headers constants", () => {
|
||||
it("Provides a TYPE header", () => {
|
||||
expect(BINARY_HEADERS_1.TYPE).to.equal("ce-type");
|
||||
});
|
||||
it("Provides a SPEC_VERSION header", () => {
|
||||
expect(BINARY_HEADERS_1.SPEC_VERSION).to.equal("ce-specversion");
|
||||
});
|
||||
it("Provides a SOURCE header", () => {
|
||||
expect(BINARY_HEADERS_1.SOURCE).to.equal("ce-source");
|
||||
});
|
||||
it("Provides an ID header", () => {
|
||||
expect(BINARY_HEADERS_1.ID).to.equal("ce-id");
|
||||
});
|
||||
it("Provides a TIME header", () => {
|
||||
expect(BINARY_HEADERS_1.TIME).to.equal("ce-time");
|
||||
});
|
||||
it("Provides a DATA_SCHEMA header", () => {
|
||||
expect(BINARY_HEADERS_1.DATA_SCHEMA).to.equal("ce-dataschema");
|
||||
});
|
||||
it("Provides a SUBJECT header", () => {
|
||||
expect(BINARY_HEADERS_1.SUBJECT).to.equal("ce-subject");
|
||||
});
|
||||
it("Provides an EXTENSIONS_PREFIX constant", () => {
|
||||
expect(BINARY_HEADERS_1.EXTENSIONS_PREFIX).to.equal("ce-");
|
||||
});
|
||||
});
|
||||
describe("V1 structured attributes constants", () => {
|
||||
it("Provides a TYPE attribute", () => {
|
||||
expect(STRUCTURED_ATTRS_1.TYPE).to.equal("type");
|
||||
});
|
||||
it("Provides a SPEC_VERSION attribute", () => {
|
||||
expect(STRUCTURED_ATTRS_1.SPEC_VERSION).to.equal("specversion");
|
||||
});
|
||||
it("Provides a SOURCE attribute", () => {
|
||||
expect(STRUCTURED_ATTRS_1.SOURCE).to.equal("source");
|
||||
});
|
||||
it("Provides an ID attribute", () => {
|
||||
expect(STRUCTURED_ATTRS_1.ID).to.equal("id");
|
||||
});
|
||||
it("Provides a TIME attribute", () => {
|
||||
expect(STRUCTURED_ATTRS_1.TIME).to.equal("time");
|
||||
});
|
||||
it("Provides a DATA_SCHEMA attribute", () => {
|
||||
expect(STRUCTURED_ATTRS_1.DATA_SCHEMA).to.equal("dataschema");
|
||||
});
|
||||
it("Provides a CONTENT_TYPE attribute", () => {
|
||||
expect(STRUCTURED_ATTRS_1.CONTENT_TYPE).to.equal("datacontenttype");
|
||||
});
|
||||
it("Provides a SUBJECT attribute", () => {
|
||||
expect(STRUCTURED_ATTRS_1.SUBJECT).to.equal("subject");
|
||||
});
|
||||
it("Provides a DATA attribute", () => {
|
||||
expect(STRUCTURED_ATTRS_1.DATA).to.equal("data");
|
||||
});
|
||||
it("Provides a DATA_BASE64 attribute", () => {
|
||||
expect(STRUCTURED_ATTRS_1.DATA_BASE64).to.equal("data_base64");
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,150 @@
|
|||
import { expect } from "chai";
|
||||
import CONSTANTS from "../src/constants";
|
||||
|
||||
describe("Constants exposed by top level exports", () => {
|
||||
it("Exports an ENCODING_BASE64 constant", () => {
|
||||
expect(CONSTANTS.ENCODING_BASE64).to.equal("base64");
|
||||
});
|
||||
it("Exports a DATA_ATTRIBUTE constant", () => {
|
||||
expect(CONSTANTS.DATA_ATTRIBUTE).to.equal("data");
|
||||
});
|
||||
it("Exports a MIME_JSON constant", () => {
|
||||
expect(CONSTANTS.MIME_JSON).to.equal("application/json");
|
||||
});
|
||||
it("Exports a MIME_OCTET_STREAM constant", () => {
|
||||
expect(CONSTANTS.MIME_OCTET_STREAM).to.equal("application/octet-stream");
|
||||
});
|
||||
it("Exports a MIME_CE constant", () => {
|
||||
expect(CONSTANTS.MIME_CE).to.equal("application/cloudevents");
|
||||
});
|
||||
it("Exports a MIME_CE_JSON constant", () => {
|
||||
expect(CONSTANTS.MIME_CE_JSON).to.equal("application/cloudevents+json");
|
||||
});
|
||||
it("Exports a HEADER_CONTENT_TYPE constant", () => {
|
||||
expect(CONSTANTS.HEADER_CONTENT_TYPE).to.equal("content-type");
|
||||
});
|
||||
it("Exports a DEFAULT_CONTENT_TYPE constant", () => {
|
||||
expect(CONSTANTS.DEFAULT_CONTENT_TYPE).to.equal(`${CONSTANTS.MIME_JSON}; charset=${CONSTANTS.CHARSET_DEFAULT}`);
|
||||
});
|
||||
it("Exports a DEFAULT_CE_CONTENT_TYPE constant", () => {
|
||||
expect(CONSTANTS.DEFAULT_CE_CONTENT_TYPE).to.equal(
|
||||
`${CONSTANTS.MIME_CE_JSON}; charset=${CONSTANTS.CHARSET_DEFAULT}`,
|
||||
);
|
||||
});
|
||||
describe("V0.3 binary headers constants", () => {
|
||||
it("Provides a TYPE header", () => {
|
||||
expect(CONSTANTS.CE_HEADERS.TYPE).to.equal("ce-type");
|
||||
});
|
||||
it("Provides a SPEC_VERSION header", () => {
|
||||
expect(CONSTANTS.CE_HEADERS.SPEC_VERSION).to.equal("ce-specversion");
|
||||
});
|
||||
it("Provides a SOURCE header", () => {
|
||||
expect(CONSTANTS.CE_HEADERS.SOURCE).to.equal("ce-source");
|
||||
});
|
||||
it("Provides an ID header", () => {
|
||||
expect(CONSTANTS.CE_HEADERS.ID).to.equal("ce-id");
|
||||
});
|
||||
it("Provides a TIME header", () => {
|
||||
expect(CONSTANTS.CE_HEADERS.TIME).to.equal("ce-time");
|
||||
});
|
||||
it("Provides a SCHEMA_URL header", () => {
|
||||
expect(CONSTANTS.BINARY_HEADERS_03.SCHEMA_URL).to.equal("ce-schemaurl");
|
||||
});
|
||||
it("Provides a CONTENT_ENCODING header", () => {
|
||||
expect(CONSTANTS.BINARY_HEADERS_03.CONTENT_ENCODING).to.equal("ce-datacontentencoding");
|
||||
});
|
||||
it("Provides a SUBJECT header", () => {
|
||||
expect(CONSTANTS.CE_HEADERS.SUBJECT).to.equal("ce-subject");
|
||||
});
|
||||
it("Provides an EXTENSIONS_PREFIX constant", () => {
|
||||
expect(CONSTANTS.EXTENSIONS_PREFIX).to.equal("ce-");
|
||||
});
|
||||
});
|
||||
describe("V0.3 structured attributes constants", () => {
|
||||
it("Provides a TYPE attribute", () => {
|
||||
expect(CONSTANTS.CE_ATTRIBUTES.TYPE).to.equal("type");
|
||||
});
|
||||
it("Provides a SPEC_VERSION attribute", () => {
|
||||
expect(CONSTANTS.CE_ATTRIBUTES.SPEC_VERSION).to.equal("specversion");
|
||||
});
|
||||
it("Provides a SOURCE attribute", () => {
|
||||
expect(CONSTANTS.CE_ATTRIBUTES.SOURCE).to.equal("source");
|
||||
});
|
||||
it("Provides an ID attribute", () => {
|
||||
expect(CONSTANTS.CE_ATTRIBUTES.ID).to.equal("id");
|
||||
});
|
||||
it("Provides a TIME attribute", () => {
|
||||
expect(CONSTANTS.CE_ATTRIBUTES.TIME).to.equal("time");
|
||||
});
|
||||
it("Provides a SCHEMA_URL attribute", () => {
|
||||
expect(CONSTANTS.STRUCTURED_ATTRS_03.SCHEMA_URL).to.equal("schemaurl");
|
||||
});
|
||||
it("Provides a CONTENT_ENCODING attribute", () => {
|
||||
expect(CONSTANTS.STRUCTURED_ATTRS_03.CONTENT_ENCODING).to.equal("datacontentencoding");
|
||||
});
|
||||
it("Provides a SUBJECT attribute", () => {
|
||||
expect(CONSTANTS.CE_ATTRIBUTES.SUBJECT).to.equal("subject");
|
||||
});
|
||||
it("Provides a DATA attribute", () => {
|
||||
expect(CONSTANTS.CE_ATTRIBUTES.DATA).to.equal("data");
|
||||
});
|
||||
});
|
||||
describe("V01 binary headers constants", () => {
|
||||
it("Provides a TYPE header", () => {
|
||||
expect(CONSTANTS.CE_HEADERS.TYPE).to.equal("ce-type");
|
||||
});
|
||||
it("Provides a SPEC_VERSION header", () => {
|
||||
expect(CONSTANTS.CE_HEADERS.SPEC_VERSION).to.equal("ce-specversion");
|
||||
});
|
||||
it("Provides a SOURCE header", () => {
|
||||
expect(CONSTANTS.CE_HEADERS.SOURCE).to.equal("ce-source");
|
||||
});
|
||||
it("Provides an ID header", () => {
|
||||
expect(CONSTANTS.CE_HEADERS.ID).to.equal("ce-id");
|
||||
});
|
||||
it("Provides a TIME header", () => {
|
||||
expect(CONSTANTS.CE_HEADERS.TIME).to.equal("ce-time");
|
||||
});
|
||||
it("Provides a DATA_SCHEMA header", () => {
|
||||
expect(CONSTANTS.BINARY_HEADERS_1.DATA_SCHEMA).to.equal("ce-dataschema");
|
||||
});
|
||||
it("Provides a SUBJECT header", () => {
|
||||
expect(CONSTANTS.CE_HEADERS.SUBJECT).to.equal("ce-subject");
|
||||
});
|
||||
it("Provides an EXTENSIONS_PREFIX constant", () => {
|
||||
expect(CONSTANTS.EXTENSIONS_PREFIX).to.equal("ce-");
|
||||
});
|
||||
});
|
||||
describe("V1 structured attributes constants", () => {
|
||||
it("Provides a TYPE attribute", () => {
|
||||
expect(CONSTANTS.CE_ATTRIBUTES.TYPE).to.equal("type");
|
||||
});
|
||||
it("Provides a SPEC_VERSION attribute", () => {
|
||||
expect(CONSTANTS.CE_ATTRIBUTES.SPEC_VERSION).to.equal("specversion");
|
||||
});
|
||||
it("Provides a SOURCE attribute", () => {
|
||||
expect(CONSTANTS.CE_ATTRIBUTES.SOURCE).to.equal("source");
|
||||
});
|
||||
it("Provides an ID attribute", () => {
|
||||
expect(CONSTANTS.CE_ATTRIBUTES.ID).to.equal("id");
|
||||
});
|
||||
it("Provides a TIME attribute", () => {
|
||||
expect(CONSTANTS.CE_ATTRIBUTES.TIME).to.equal("time");
|
||||
});
|
||||
it("Provides a DATA_SCHEMA attribute", () => {
|
||||
expect(CONSTANTS.STRUCTURED_ATTRS_1.DATA_SCHEMA).to.equal("dataschema");
|
||||
});
|
||||
it("Provides a CONTENT_TYPE attribute", () => {
|
||||
expect(CONSTANTS.CE_ATTRIBUTES.CONTENT_TYPE).to.equal("datacontenttype");
|
||||
});
|
||||
it("Provides a SUBJECT attribute", () => {
|
||||
expect(CONSTANTS.CE_ATTRIBUTES.SUBJECT).to.equal("subject");
|
||||
});
|
||||
it("Provides a DATA attribute", () => {
|
||||
expect(CONSTANTS.CE_ATTRIBUTES.DATA).to.equal("data");
|
||||
});
|
||||
it("Provides a DATA_BASE64 attribute", () => {
|
||||
expect(CONSTANTS.STRUCTURED_ATTRS_1.DATA_BASE64).to.equal("data_base64");
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,206 @@
|
|||
import "mocha";
|
||||
import { expect } from "chai";
|
||||
import nock from "nock";
|
||||
|
||||
import { emitBinary, emitStructured } from "../src/transport/http";
|
||||
import { CloudEvent, Version } from "../src";
|
||||
import { AxiosResponse } from "axios";
|
||||
|
||||
const type = "com.github.pull.create";
|
||||
const source = "urn:event:from:myapi/resourse/123";
|
||||
const contentEncoding = "base64";
|
||||
const contentType = "application/cloudevents+json; charset=utf-8";
|
||||
const time = new Date();
|
||||
const schemaurl = "http://cloudevents.io/schema.json";
|
||||
|
||||
const ceContentType = "application/json";
|
||||
|
||||
const data = {
|
||||
foo: "bar",
|
||||
};
|
||||
const dataBase64 = "Y2xvdWRldmVudHMK";
|
||||
|
||||
const ext1Name = "extension1";
|
||||
const ext1Value = "foobar";
|
||||
const ext2Name = "extension2";
|
||||
const ext2Value = "acme";
|
||||
|
||||
const cloudevent = new CloudEvent({
|
||||
specversion: Version.V03,
|
||||
type,
|
||||
source,
|
||||
datacontenttype: ceContentType,
|
||||
subject: "subject.ext",
|
||||
time,
|
||||
schemaurl,
|
||||
data,
|
||||
// set these so that deepEqual works
|
||||
dataschema: "",
|
||||
datacontentencoding: "",
|
||||
data_base64: "",
|
||||
});
|
||||
cloudevent[ext1Name] = ext1Value;
|
||||
cloudevent[ext2Name] = ext2Value;
|
||||
|
||||
const cebase64 = new CloudEvent({
|
||||
specversion: Version.V03,
|
||||
type,
|
||||
source,
|
||||
datacontenttype: ceContentType,
|
||||
datacontentencoding: contentEncoding,
|
||||
time,
|
||||
schemaurl,
|
||||
data: dataBase64,
|
||||
});
|
||||
cebase64[ext1Name] = ext1Value;
|
||||
cebase64[ext2Name] = ext2Value;
|
||||
|
||||
const webhook = "https://cloudevents.io/webhook";
|
||||
const httpcfg = {
|
||||
method: "POST",
|
||||
url: `${webhook}/json`,
|
||||
};
|
||||
|
||||
describe("HTTP Transport Binding - Version 0.3", () => {
|
||||
beforeEach(() => {
|
||||
// Mocking the webhook
|
||||
nock(webhook).post("/json").reply(201, { status: "accepted" });
|
||||
});
|
||||
|
||||
describe("Structured", () => {
|
||||
describe("JSON Format", () => {
|
||||
it(`requires '${contentType}' Content-Type in the header`, () =>
|
||||
emitStructured(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(response.config.headers["Content-Type"]).to.equal(contentType);
|
||||
}));
|
||||
|
||||
it("the request payload should be correct", () =>
|
||||
emitStructured(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(response.config.data).to.deep.equal(JSON.stringify(cloudevent));
|
||||
}));
|
||||
|
||||
describe("'data' attribute with 'base64' encoding", () => {
|
||||
it("the request payload should be correct", () =>
|
||||
emitStructured(cebase64, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(JSON.parse(response.config.data).data).to.equal(cebase64.data);
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Binary", () => {
|
||||
describe("JSON Format", () => {
|
||||
it(`requires ${cloudevent.datacontenttype} in the header`, () =>
|
||||
emitBinary(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(response.config.headers["Content-Type"]).to.equal(cloudevent.datacontenttype);
|
||||
}));
|
||||
|
||||
it("the request payload should be correct", () =>
|
||||
emitBinary(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(JSON.parse(response.config.data)).to.deep.equal(cloudevent.data);
|
||||
}));
|
||||
|
||||
it("HTTP Header contains 'ce-type'", () =>
|
||||
emitBinary(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(response.config.headers).to.have.property("ce-type");
|
||||
}));
|
||||
|
||||
it("HTTP Header contains 'ce-specversion'", () =>
|
||||
emitBinary(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(response.config.headers).to.have.property("ce-specversion");
|
||||
}));
|
||||
|
||||
it("HTTP Header contains 'ce-source'", () =>
|
||||
emitBinary(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(response.config.headers).to.have.property("ce-source");
|
||||
}));
|
||||
|
||||
it("HTTP Header contains 'ce-id'", () =>
|
||||
emitBinary(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(response.config.headers).to.have.property("ce-id");
|
||||
}));
|
||||
|
||||
it("HTTP Header contains 'ce-time'", () =>
|
||||
emitBinary(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(response.config.headers).to.have.property("ce-time");
|
||||
}));
|
||||
|
||||
it("HTTP Header contains 'ce-schemaurl'", () =>
|
||||
emitBinary(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(response.config.headers).to.have.property("ce-schemaurl");
|
||||
}));
|
||||
|
||||
it(`HTTP Header contains 'ce-${ext1Name}'`, () =>
|
||||
emitBinary(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(response.config.headers).to.have.property(`ce-${ext1Name}`);
|
||||
}));
|
||||
|
||||
it(`HTTP Header contains 'ce-${ext2Name}'`, () =>
|
||||
emitBinary(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(response.config.headers).to.have.property(`ce-${ext2Name}`);
|
||||
}));
|
||||
|
||||
it("HTTP Header contains 'ce-subject'", () =>
|
||||
emitBinary(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(response.config.headers).to.have.property("ce-subject");
|
||||
}));
|
||||
|
||||
it("should 'ce-type' have the right value", () =>
|
||||
emitBinary(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(cloudevent.type).to.equal(response.config.headers["ce-type"]);
|
||||
}));
|
||||
|
||||
it("should 'ce-specversion' have the right value", () =>
|
||||
emitBinary(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(cloudevent.specversion).to.equal(response.config.headers["ce-specversion"]);
|
||||
}));
|
||||
|
||||
it("should 'ce-source' have the right value", () =>
|
||||
emitBinary(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(cloudevent.source).to.equal(response.config.headers["ce-source"]);
|
||||
}));
|
||||
|
||||
it("should 'ce-id' have the right value", () =>
|
||||
emitBinary(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(cloudevent.id).to.equal(response.config.headers["ce-id"]);
|
||||
}));
|
||||
|
||||
it("should 'ce-time' have the right value", () =>
|
||||
emitBinary(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(cloudevent.time).to.equal(response.config.headers["ce-time"]);
|
||||
}));
|
||||
|
||||
it("should 'ce-schemaurl' have the right value", () =>
|
||||
emitBinary(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(cloudevent.schemaurl).to.equal(response.config.headers["ce-schemaurl"]);
|
||||
}));
|
||||
|
||||
it(`should 'ce-${ext1Name}' have the right value`, () =>
|
||||
emitBinary(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(cloudevent[ext1Name]).to.equal(response.config.headers[`ce-${ext1Name}`]);
|
||||
}));
|
||||
|
||||
it(`should 'ce-${ext2Name}' have the right value`, () =>
|
||||
emitBinary(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(cloudevent[ext2Name]).to.equal(response.config.headers[`ce-${ext2Name}`]);
|
||||
}));
|
||||
|
||||
it("should 'ce-subject' have the right value", () =>
|
||||
emitBinary(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(cloudevent.subject).to.equal(response.config.headers["ce-subject"]);
|
||||
}));
|
||||
|
||||
describe("'data' attribute with 'base64' encoding", () => {
|
||||
it("HTTP Header contains 'ce-datacontentencoding'", () =>
|
||||
emitBinary(cebase64, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(response.config.headers).to.have.property("ce-datacontentencoding");
|
||||
}));
|
||||
|
||||
it("should 'ce-datacontentencoding' have the right value", () =>
|
||||
emitBinary(cebase64, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(cebase64.datacontentencoding).to.equal(response.config.headers["ce-datacontentencoding"]);
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,245 +0,0 @@
|
|||
const expect = require("chai").expect;
|
||||
const nock = require("nock");
|
||||
const BinaryHTTPEmitter = require("../lib/bindings/http/emitter_binary.js");
|
||||
const StructuredHTTPEmitter = require("../lib/bindings/http/emitter_structured.js");
|
||||
const { CloudEvent } = require("../");
|
||||
const {
|
||||
SPEC_V03
|
||||
} = require("../lib/bindings/http/constants.js");
|
||||
|
||||
const type = "com.github.pull.create";
|
||||
const source = "urn:event:from:myapi/resourse/123";
|
||||
const contentEncoding = "base64";
|
||||
const contentType = "application/cloudevents+json; charset=utf-8";
|
||||
const time = new Date();
|
||||
const schemaURL = "http://cloudevents.io/schema.json";
|
||||
|
||||
const ceContentType = "application/json";
|
||||
|
||||
const data = {
|
||||
foo: "bar"
|
||||
};
|
||||
const dataBase64 = "Y2xvdWRldmVudHMK";
|
||||
|
||||
const ext1Name = "extension1";
|
||||
const ext1Value = "foobar";
|
||||
const ext2Name = "extension2";
|
||||
const ext2Value = "acme";
|
||||
|
||||
const cloudevent = new CloudEvent({
|
||||
specversion: SPEC_V03,
|
||||
type,
|
||||
source,
|
||||
dataContentType: ceContentType,
|
||||
subject: "subject.ext",
|
||||
time,
|
||||
schemaURL,
|
||||
data
|
||||
});
|
||||
cloudevent.addExtension(ext1Name, ext1Value);
|
||||
cloudevent.addExtension(ext2Name, ext2Value);
|
||||
|
||||
const cebase64 = new CloudEvent({
|
||||
specversion: SPEC_V03,
|
||||
type,
|
||||
source,
|
||||
dataContentType: ceContentType,
|
||||
dataContentEncoding: contentEncoding,
|
||||
time,
|
||||
schemaURL,
|
||||
data: dataBase64
|
||||
});
|
||||
cebase64.addExtension(ext1Name, ext1Value);
|
||||
cebase64.addExtension(ext2Name, ext2Value);
|
||||
|
||||
const webhook = "https://cloudevents.io/webhook";
|
||||
const httpcfg = {
|
||||
method: "POST",
|
||||
url: `${webhook}/json`
|
||||
};
|
||||
|
||||
const binary = new BinaryHTTPEmitter(SPEC_V03);
|
||||
const structured = new StructuredHTTPEmitter();
|
||||
|
||||
describe("HTTP Transport Binding - Version 0.3", () => {
|
||||
beforeEach(() => {
|
||||
// Mocking the webhook
|
||||
nock(webhook)
|
||||
.post("/json")
|
||||
.reply(201, { status: "accepted" });
|
||||
});
|
||||
|
||||
describe("Structured", () => {
|
||||
describe("JSON Format", () => {
|
||||
it(`requires '${contentType}' Content-Type in the header`,
|
||||
() => structured.emit(httpcfg, cloudevent)
|
||||
.then((response) => {
|
||||
expect(response.config.headers["Content-Type"])
|
||||
.to.equal(contentType);
|
||||
}));
|
||||
|
||||
it("the request payload should be correct",
|
||||
() => structured.emit(httpcfg, cloudevent)
|
||||
.then((response) => {
|
||||
expect(JSON.parse(response.config.data))
|
||||
.to.deep.equal(cloudevent.format());
|
||||
}));
|
||||
|
||||
describe("'data' attribute with 'base64' encoding", () => {
|
||||
it("the request payload should be correct",
|
||||
() => structured.emit(httpcfg, cebase64)
|
||||
.then((response) => {
|
||||
expect(JSON.parse(response.config.data).data)
|
||||
.to.equal(cebase64.format().data);
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Binary", () => {
|
||||
describe("JSON Format", () => {
|
||||
it(`requires ${cloudevent.dataContentType} in the header`,
|
||||
() => binary.emit(httpcfg, cloudevent)
|
||||
.then((response) => {
|
||||
expect(response.config.headers["Content-Type"])
|
||||
.to.equal(cloudevent.dataContentType);
|
||||
}));
|
||||
|
||||
it("the request payload should be correct", () => binary.emit(httpcfg, cloudevent)
|
||||
.then((response) => {
|
||||
expect(JSON.parse(response.config.data))
|
||||
.to.deep.equal(cloudevent.data);
|
||||
}));
|
||||
|
||||
it("HTTP Header contains 'ce-type'", () => binary.emit(httpcfg, cloudevent)
|
||||
.then((response) => {
|
||||
expect(response.config.headers)
|
||||
.to.have.property("ce-type");
|
||||
}));
|
||||
|
||||
it("HTTP Header contains 'ce-specversion'", () => binary.emit(httpcfg, cloudevent)
|
||||
.then((response) => {
|
||||
expect(response.config.headers)
|
||||
.to.have.property("ce-specversion");
|
||||
}));
|
||||
|
||||
it("HTTP Header contains 'ce-source'", () => binary.emit(httpcfg, cloudevent)
|
||||
.then((response) => {
|
||||
expect(response.config.headers)
|
||||
.to.have.property("ce-source");
|
||||
}));
|
||||
|
||||
it("HTTP Header contains 'ce-id'", () => binary.emit(httpcfg, cloudevent)
|
||||
.then((response) => {
|
||||
expect(response.config.headers)
|
||||
.to.have.property("ce-id");
|
||||
}));
|
||||
|
||||
it("HTTP Header contains 'ce-time'", () => binary.emit(httpcfg, cloudevent)
|
||||
.then((response) => {
|
||||
expect(response.config.headers)
|
||||
.to.have.property("ce-time");
|
||||
}));
|
||||
|
||||
it("HTTP Header contains 'ce-schemaurl'", () => binary.emit(httpcfg, cloudevent)
|
||||
.then((response) => {
|
||||
expect(response.config.headers)
|
||||
.to.have.property("ce-schemaurl");
|
||||
}));
|
||||
|
||||
it(`HTTP Header contains 'ce-${ext1Name}'`, () => binary.emit(httpcfg, cloudevent)
|
||||
.then((response) => {
|
||||
expect(response.config.headers)
|
||||
.to.have.property(`ce-${ext1Name}`);
|
||||
}));
|
||||
|
||||
it(`HTTP Header contains 'ce-${ext2Name}'`, () => binary.emit(httpcfg, cloudevent)
|
||||
.then((response) => {
|
||||
expect(response.config.headers)
|
||||
.to.have.property(`ce-${ext2Name}`);
|
||||
}));
|
||||
|
||||
it("HTTP Header contains 'ce-subject'", () => binary.emit(httpcfg, cloudevent)
|
||||
.then((response) => {
|
||||
expect(response.config.headers)
|
||||
.to.have.property("ce-subject");
|
||||
}));
|
||||
|
||||
it("should 'ce-type' have the right value", () => binary.emit(httpcfg, cloudevent)
|
||||
.then((response) => {
|
||||
expect(cloudevent.type)
|
||||
.to.equal(response.config.headers["ce-type"]);
|
||||
}));
|
||||
|
||||
it("should 'ce-specversion' have the right value",
|
||||
() => binary.emit(httpcfg, cloudevent)
|
||||
.then((response) => {
|
||||
expect(cloudevent.specversion)
|
||||
.to.equal(response.config.headers["ce-specversion"]);
|
||||
}));
|
||||
|
||||
it("should 'ce-source' have the right value",
|
||||
() => binary.emit(httpcfg, cloudevent)
|
||||
.then((response) => {
|
||||
expect(cloudevent.source)
|
||||
.to.equal(response.config.headers["ce-source"]);
|
||||
}));
|
||||
|
||||
it("should 'ce-id' have the right value", () => binary.emit(httpcfg, cloudevent)
|
||||
.then((response) => {
|
||||
expect(cloudevent.id)
|
||||
.to.equal(response.config.headers["ce-id"]);
|
||||
}));
|
||||
|
||||
it("should 'ce-time' have the right value", () => binary.emit(httpcfg, cloudevent)
|
||||
.then((response) => {
|
||||
expect(cloudevent.time)
|
||||
.to.equal(response.config.headers["ce-time"]);
|
||||
}));
|
||||
|
||||
it("should 'ce-schemaurl' have the right value",
|
||||
() => binary.emit(httpcfg, cloudevent)
|
||||
.then((response) => {
|
||||
expect(cloudevent.schemaURL)
|
||||
.to.equal(response.config.headers["ce-schemaurl"]);
|
||||
}));
|
||||
|
||||
it(`should 'ce-${ext1Name}' have the right value`,
|
||||
() => binary.emit(httpcfg, cloudevent)
|
||||
.then((response) => {
|
||||
expect(cloudevent.getExtensions()[ext1Name])
|
||||
.to.equal(response.config.headers[`ce-${ext1Name}`]);
|
||||
}));
|
||||
|
||||
it(`should 'ce-${ext2Name}' have the right value`,
|
||||
() => binary.emit(httpcfg, cloudevent)
|
||||
.then((response) => {
|
||||
expect(cloudevent.getExtensions()[ext2Name])
|
||||
.to.equal(response.config.headers[`ce-${ext2Name}`]);
|
||||
}));
|
||||
|
||||
it("should 'ce-subject' have the right value",
|
||||
() => binary.emit(httpcfg, cloudevent)
|
||||
.then((response) => {
|
||||
expect(cloudevent.subject)
|
||||
.to.equal(response.config.headers["ce-subject"]);
|
||||
}));
|
||||
|
||||
describe("'data' attribute with 'base64' encoding", () => {
|
||||
it("HTTP Header contains 'ce-datacontentencoding'",
|
||||
() => binary.emit(httpcfg, cebase64)
|
||||
.then((response) => {
|
||||
expect(response.config.headers)
|
||||
.to.have.property("ce-datacontentencoding");
|
||||
}));
|
||||
|
||||
it("should 'ce-datacontentencoding' have the right value",
|
||||
() => binary.emit(httpcfg, cebase64)
|
||||
.then((response) => {
|
||||
expect(cebase64.dataContentEncoding)
|
||||
.to.equal(response.config.headers["ce-datacontentencoding"]);
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,293 +0,0 @@
|
|||
const expect = require("chai").expect;
|
||||
const nock = require("nock");
|
||||
const https = require("https");
|
||||
const { asBase64 } = require("../lib/bindings/http/validation/fun.js");
|
||||
const { SPEC_V1 } = require("../lib/bindings/http/constants.js");
|
||||
const { CloudEvent } = require("../");
|
||||
const BinaryHTTPEmitter = require("../lib/bindings/http/emitter_binary.js");
|
||||
const StructuredHTTPEmitter = require("../lib/bindings/http/emitter_structured.js");
|
||||
const type = "com.github.pull.create";
|
||||
const source = "urn:event:from:myapi/resource/123";
|
||||
const contentType = "application/cloudevents+json; charset=utf-8";
|
||||
const time = new Date();
|
||||
const subject = "subject.ext";
|
||||
const dataSchema = "http://cloudevents.io/schema.json";
|
||||
|
||||
const dataContentType = "application/json";
|
||||
|
||||
const data = {
|
||||
foo: "bar"
|
||||
};
|
||||
|
||||
const ext1Name = "extension1";
|
||||
const ext1Value = "foobar";
|
||||
const ext2Name = "extension2";
|
||||
const ext2Value = "acme";
|
||||
|
||||
const cloudevent = new CloudEvent({
|
||||
specversion: SPEC_V1,
|
||||
type,
|
||||
source,
|
||||
dataContentType,
|
||||
subject,
|
||||
time,
|
||||
dataSchema,
|
||||
data
|
||||
});
|
||||
cloudevent.addExtension(ext1Name, ext1Value);
|
||||
cloudevent.addExtension(ext2Name, ext2Value);
|
||||
|
||||
const dataString = ")(*~^my data for ce#@#$%";
|
||||
|
||||
const webhook = "https://cloudevents.io/webhook/v1";
|
||||
const httpcfg = {
|
||||
method: "POST",
|
||||
url: `${webhook}/json`
|
||||
};
|
||||
|
||||
const binary = new BinaryHTTPEmitter(SPEC_V1);
|
||||
const structured = new StructuredHTTPEmitter();
|
||||
|
||||
describe("HTTP Transport Binding - Version 1.0", () => {
|
||||
beforeEach(() => {
|
||||
// Mocking the webhook
|
||||
nock(webhook)
|
||||
.post("/json")
|
||||
.reply(201, { status: "accepted" });
|
||||
});
|
||||
|
||||
describe("Structured", () => {
|
||||
it("works with mTLS authentication", () => {
|
||||
const event = new StructuredHTTPEmitter({
|
||||
method: "POST",
|
||||
url: `${webhook}/json`,
|
||||
httpsAgent: new https.Agent({
|
||||
cert: "some value",
|
||||
key: "other value"
|
||||
})
|
||||
});
|
||||
return event.emit(httpcfg, cloudevent).then((response) => {
|
||||
expect(response.config.headers["Content-Type"])
|
||||
.to.equal(contentType);
|
||||
});
|
||||
});
|
||||
|
||||
describe("JSON Format", () => {
|
||||
it(`requires '${contentType}' Content-Type in the header`,
|
||||
() => structured.emit(httpcfg, cloudevent)
|
||||
.then((response) => {
|
||||
expect(response.config.headers["Content-Type"])
|
||||
.to.equal(contentType);
|
||||
}));
|
||||
|
||||
it("the request payload should be correct",
|
||||
() => structured.emit(httpcfg, cloudevent)
|
||||
.then((response) => {
|
||||
expect(JSON.parse(response.config.data))
|
||||
.to.deep.equal(cloudevent.format());
|
||||
}));
|
||||
|
||||
describe("Binary event data", () => {
|
||||
it("the request payload should be correct when data is binary", () => {
|
||||
const bindata = Uint32Array.from(dataString, (c) => c.codePointAt(0));
|
||||
const expected = asBase64(bindata);
|
||||
const binevent = new CloudEvent({
|
||||
type,
|
||||
source,
|
||||
dataContentType: "text/plain",
|
||||
data: bindata,
|
||||
});
|
||||
binevent.addExtension(ext1Name, ext1Value);
|
||||
binevent.addExtension(ext2Name, ext2Value);
|
||||
|
||||
return structured.emit(httpcfg, binevent)
|
||||
.then((response) => {
|
||||
expect(JSON.parse(response.config.data).data_base64)
|
||||
.to.equal(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it("the payload must have 'data_base64' when data is binary", () => {
|
||||
const binevent = new CloudEvent({
|
||||
type,
|
||||
source,
|
||||
dataContentType: "text/plain",
|
||||
data: Uint32Array.from(dataString, (c) => c.codePointAt(0)),
|
||||
});
|
||||
binevent.addExtension(ext1Name, ext1Value);
|
||||
binevent.addExtension(ext2Name, ext2Value);
|
||||
|
||||
return structured.emit(httpcfg, binevent)
|
||||
.then((response) => {
|
||||
expect(JSON.parse(response.config.data))
|
||||
.to.have.property("data_base64");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// describe("Binary", () => {
|
||||
// it("works with mTLS authentication", () =>
|
||||
// binary.emit({
|
||||
// method: "POST",
|
||||
// url: `${webhook}/json`,
|
||||
// httpsAgent: new https.Agent({
|
||||
// cert: "some value",
|
||||
// key: "other value"
|
||||
// })
|
||||
// }, cloudevent).then((response) => {
|
||||
// expect(response.config.headers["Content-Type"])
|
||||
// .to.equal(cloudevent.dataContentType);
|
||||
// })
|
||||
// );
|
||||
|
||||
// describe("JSON Format", () => {
|
||||
// it(`requires '${cloudevent.dataContentType}' in the header`,
|
||||
// () => binary.emit(httpcfg, cloudevent)
|
||||
// .then((response) => {
|
||||
// expect(response.config.headers["Content-Type"])
|
||||
// .to.equal(cloudevent.dataContentType);
|
||||
// }));
|
||||
|
||||
// it("the request payload should be correct", () => binary.emit(httpcfg, cloudevent)
|
||||
// .then((response) => {
|
||||
// expect(JSON.parse(response.config.data))
|
||||
// .to.deep.equal(cloudevent.data);
|
||||
// }));
|
||||
|
||||
// it("the request payload should be correct when event data is binary", () => {
|
||||
// const bindata = Uint32Array.from(dataString, (c) => c.codePointAt(0));
|
||||
// const expected = asBase64(bindata);
|
||||
// const binevent = new CloudEvent({
|
||||
// type,
|
||||
// source,
|
||||
// dataContentType: "text/plain",
|
||||
// data: bindata,
|
||||
// });
|
||||
// binevent.addExtension(ext1Name, ext1Value);
|
||||
// binevent.addExtension(ext2Name, ext2Value);
|
||||
|
||||
// return binary.emit(httpcfg, binevent)
|
||||
// .then((response) => {
|
||||
// expect(response.config.data)
|
||||
// .to.equal(expected);
|
||||
// });
|
||||
// });
|
||||
|
||||
// it("HTTP Header contains 'ce-type'", () => binary.emit(httpcfg, cloudevent)
|
||||
// .then((response) => {
|
||||
// expect(response.config.headers)
|
||||
// .to.have.property("ce-type");
|
||||
// }));
|
||||
|
||||
// it("HTTP Header contains 'ce-specversion'", () => binary.emit(httpcfg, cloudevent)
|
||||
// .then((response) => {
|
||||
// expect(response.config.headers)
|
||||
// .to.have.property("ce-specversion");
|
||||
// }));
|
||||
|
||||
// it("HTTP Header contains 'ce-source'", () => binary.emit(httpcfg, cloudevent)
|
||||
// .then((response) => {
|
||||
// expect(response.config.headers)
|
||||
// .to.have.property("ce-source");
|
||||
// }));
|
||||
|
||||
// it("HTTP Header contains 'ce-id'", () => binary.emit(httpcfg, cloudevent)
|
||||
// .then((response) => {
|
||||
// expect(response.config.headers)
|
||||
// .to.have.property("ce-id");
|
||||
// }));
|
||||
|
||||
// it("HTTP Header contains 'ce-time'", () => binary.emit(httpcfg, cloudevent)
|
||||
// .then((response) => {
|
||||
// expect(response.config.headers)
|
||||
// .to.have.property("ce-time");
|
||||
// }));
|
||||
|
||||
// it("HTTP Header contains 'ce-dataschema'", () => binary.emit(httpcfg, cloudevent)
|
||||
// .then((response) => {
|
||||
// expect(response.config.headers)
|
||||
// .to.have.property("ce-dataschema");
|
||||
// }));
|
||||
|
||||
// it(`HTTP Header contains 'ce-${ext1Name}'`, () => binary.emit(httpcfg, cloudevent)
|
||||
// .then((response) => {
|
||||
// expect(response.config.headers)
|
||||
// .to.have.property(`ce-${ext1Name}`);
|
||||
// }));
|
||||
|
||||
// it(`HTTP Header contains 'ce-${ext2Name}'`, () => binary.emit(httpcfg, cloudevent)
|
||||
// .then((response) => {
|
||||
// expect(response.config.headers)
|
||||
// .to.have.property(`ce-${ext2Name}`);
|
||||
// }));
|
||||
|
||||
// it("HTTP Header contains 'ce-subject'", () => binary.emit(httpcfg, cloudevent)
|
||||
// .then((response) => {
|
||||
// expect(response.config.headers)
|
||||
// .to.have.property("ce-subject");
|
||||
// }));
|
||||
|
||||
// it("should 'ce-type' have the right value", () => binary.emit(httpcfg, cloudevent)
|
||||
// .then((response) => {
|
||||
// expect(cloudevent.type)
|
||||
// .to.equal(response.config.headers["ce-type"]);
|
||||
// }));
|
||||
|
||||
// it("should 'ce-specversion' have the right value",
|
||||
// () => binary.emit(httpcfg, cloudevent)
|
||||
// .then((response) => {
|
||||
// expect(cloudevent.specversion)
|
||||
// .to.equal(response.config.headers["ce-specversion"]);
|
||||
// }));
|
||||
|
||||
// it("should 'ce-source' have the right value",
|
||||
// () => binary.emit(httpcfg, cloudevent)
|
||||
// .then((response) => {
|
||||
// expect(cloudevent.source)
|
||||
// .to.equal(response.config.headers["ce-source"]);
|
||||
// }));
|
||||
|
||||
// it("should 'ce-id' have the right value", () => binary.emit(httpcfg, cloudevent)
|
||||
// .then((response) => {
|
||||
// expect(cloudevent.id)
|
||||
// .to.equal(response.config.headers["ce-id"]);
|
||||
// }));
|
||||
|
||||
// it("should 'ce-time' have the right value", () => binary.emit(httpcfg, cloudevent)
|
||||
// .then((response) => {
|
||||
// expect(cloudevent.time)
|
||||
// .to.equal(response.config.headers["ce-time"]);
|
||||
// }));
|
||||
|
||||
// it("should 'ce-dataschema' have the right value",
|
||||
// () => binary.emit(httpcfg, cloudevent)
|
||||
// .then((response) => {
|
||||
// expect(cloudevent.dataSchema)
|
||||
// .to.equal(response.config.headers["ce-dataschema"]);
|
||||
// }));
|
||||
|
||||
// it(`should 'ce-${ext1Name}' have the right value`,
|
||||
// () => binary.emit(httpcfg, cloudevent)
|
||||
// .then((response) => {
|
||||
// expect(cloudevent.getExtensions()[ext1Name])
|
||||
// .to.equal(response.config.headers[`ce-${ext1Name}`]);
|
||||
// }));
|
||||
|
||||
// it(`should 'ce-${ext2Name}' have the right value`,
|
||||
// () => binary.emit(httpcfg, cloudevent)
|
||||
// .then((response) => {
|
||||
// expect(cloudevent.getExtensions()[ext2Name])
|
||||
// .to.equal(response.config.headers[`ce-${ext2Name}`]);
|
||||
// }));
|
||||
|
||||
// it("should 'ce-subject' have the right value",
|
||||
// () => binary.emit(httpcfg, cloudevent)
|
||||
// .then((response) => {
|
||||
// expect(cloudevent.subject)
|
||||
// .to.equal(response.config.headers["ce-subject"]);
|
||||
// }));
|
||||
// });
|
||||
// });
|
||||
});
|
|
@ -0,0 +1,240 @@
|
|||
import * as https from "https";
|
||||
import "mocha";
|
||||
import { expect } from "chai";
|
||||
import nock from "nock";
|
||||
|
||||
import { CloudEvent, Version } from "../src";
|
||||
import { emitBinary, emitStructured } from "../src/transport/http";
|
||||
import { asBase64 } from "../src/event/validation/is";
|
||||
import { AxiosResponse } from "axios";
|
||||
|
||||
const type = "com.github.pull.create";
|
||||
const source = "urn:event:from:myapi/resource/123";
|
||||
const contentType = "application/cloudevents+json; charset=utf-8";
|
||||
const time = new Date();
|
||||
const subject = "subject.ext";
|
||||
const dataschema = "http://cloudevents.io/schema.json";
|
||||
const datacontenttype = "application/json";
|
||||
|
||||
const data = {
|
||||
foo: "bar",
|
||||
};
|
||||
|
||||
const ext1Name = "extension1";
|
||||
const ext1Value = "foobar";
|
||||
const ext2Name = "extension2";
|
||||
const ext2Value = "acme";
|
||||
|
||||
const cloudevent = new CloudEvent({
|
||||
specversion: Version.V1,
|
||||
type,
|
||||
source,
|
||||
datacontenttype,
|
||||
subject,
|
||||
time,
|
||||
dataschema,
|
||||
data,
|
||||
});
|
||||
cloudevent[ext1Name] = ext1Value;
|
||||
cloudevent[ext2Name] = ext2Value;
|
||||
|
||||
const dataString = ")(*~^my data for ce#@#$%";
|
||||
|
||||
const webhook = "https://cloudevents.io/webhook/v1";
|
||||
const httpcfg = { url: `${webhook}/json` };
|
||||
|
||||
describe("HTTP Transport Binding - Version 1.0", () => {
|
||||
beforeEach(() => {
|
||||
// Mocking the webhook
|
||||
nock(webhook).post("/json").reply(201, { status: "accepted" });
|
||||
});
|
||||
|
||||
describe("Structured", () => {
|
||||
it("works with mTLS authentication", () => {
|
||||
const httpsAgent = new https.Agent({
|
||||
cert: "some value",
|
||||
key: "other value",
|
||||
});
|
||||
|
||||
return emitStructured(cloudevent, { ...httpcfg, httpsAgent }).then((response: AxiosResponse) => {
|
||||
expect(response.config.headers["Content-Type"]).to.equal(contentType);
|
||||
});
|
||||
});
|
||||
|
||||
describe("JSON Format", () => {
|
||||
it(`requires '${contentType}' Content-Type in the header`, () =>
|
||||
emitStructured(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(response.config.headers["Content-Type"]).to.equal(contentType);
|
||||
}));
|
||||
|
||||
it("the request payload should be correct", () =>
|
||||
emitStructured(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(response.config.data).to.deep.equal(JSON.stringify(cloudevent));
|
||||
}));
|
||||
|
||||
describe("Binary event data", () => {
|
||||
it("the request payload should be correct when data is binary", () => {
|
||||
const bindata = Uint32Array.from(dataString as string, (c) => c.codePointAt(0) as number);
|
||||
const expected = asBase64(bindata);
|
||||
const binevent = new CloudEvent({
|
||||
type,
|
||||
source,
|
||||
datacontenttype: "text/plain",
|
||||
data: bindata,
|
||||
});
|
||||
binevent[ext1Name] = ext1Value;
|
||||
binevent[ext2Name] = ext2Value;
|
||||
|
||||
return emitStructured(binevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(JSON.parse(response.config.data).data_base64).to.equal(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it("the payload must have 'data_base64' when data is binary", () => {
|
||||
const binevent = new CloudEvent({
|
||||
type,
|
||||
source,
|
||||
datacontenttype: "text/plain",
|
||||
data: Uint32Array.from(dataString as string, (c) => c.codePointAt(0) as number),
|
||||
});
|
||||
binevent[ext1Name] = ext1Value;
|
||||
binevent[ext2Name] = ext2Value;
|
||||
|
||||
return emitStructured(binevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(JSON.parse(response.config.data)).to.have.property("data_base64");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Binary", () => {
|
||||
it("works with mTLS authentication", () =>
|
||||
emitBinary(cloudevent, {
|
||||
url: `${webhook}/json`,
|
||||
httpsAgent: new https.Agent({
|
||||
cert: "some value",
|
||||
key: "other value",
|
||||
}),
|
||||
}).then((response: AxiosResponse) => {
|
||||
expect(response.config.headers["Content-Type"]).to.equal(cloudevent.datacontenttype);
|
||||
}));
|
||||
|
||||
describe("JSON Format", () => {
|
||||
it(`requires '${cloudevent.datacontenttype}' in the header`, () =>
|
||||
emitBinary(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(response.config.headers["Content-Type"]).to.equal(cloudevent.datacontenttype);
|
||||
}));
|
||||
|
||||
it("the request payload should be correct", () =>
|
||||
emitBinary(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(JSON.parse(response.config.data)).to.deep.equal(cloudevent.data);
|
||||
}));
|
||||
|
||||
it("the request payload should be correct when event data is binary", () => {
|
||||
const bindata = Uint32Array.from(dataString as string, (c) => c.codePointAt(0) as number);
|
||||
const expected = asBase64(bindata);
|
||||
const binevent = new CloudEvent({
|
||||
type,
|
||||
source,
|
||||
datacontenttype: "text/plain",
|
||||
data: bindata,
|
||||
});
|
||||
|
||||
return emitBinary(binevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(response.config.data).to.equal(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it("HTTP Header contains 'ce-type'", () =>
|
||||
emitBinary(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(response.config.headers).to.have.property("ce-type");
|
||||
}));
|
||||
|
||||
it("HTTP Header contains 'ce-specversion'", () =>
|
||||
emitBinary(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(response.config.headers).to.have.property("ce-specversion");
|
||||
}));
|
||||
|
||||
it("HTTP Header contains 'ce-source'", () =>
|
||||
emitBinary(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(response.config.headers).to.have.property("ce-source");
|
||||
}));
|
||||
|
||||
it("HTTP Header contains 'ce-id'", () =>
|
||||
emitBinary(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(response.config.headers).to.have.property("ce-id");
|
||||
}));
|
||||
|
||||
it("HTTP Header contains 'ce-time'", () =>
|
||||
emitBinary(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(response.config.headers).to.have.property("ce-time");
|
||||
}));
|
||||
|
||||
it("HTTP Header contains 'ce-dataschema'", () =>
|
||||
emitBinary(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(response.config.headers).to.have.property("ce-dataschema");
|
||||
}));
|
||||
|
||||
it(`HTTP Header contains 'ce-${ext1Name}'`, () =>
|
||||
emitBinary(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(response.config.headers).to.have.property(`ce-${ext1Name}`);
|
||||
}));
|
||||
|
||||
it(`HTTP Header contains 'ce-${ext2Name}'`, () =>
|
||||
emitBinary(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(response.config.headers).to.have.property(`ce-${ext2Name}`);
|
||||
}));
|
||||
|
||||
it("HTTP Header contains 'ce-subject'", () =>
|
||||
emitBinary(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(response.config.headers).to.have.property("ce-subject");
|
||||
}));
|
||||
|
||||
it("should 'ce-type' have the right value", () =>
|
||||
emitBinary(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(cloudevent.type).to.equal(response.config.headers["ce-type"]);
|
||||
}));
|
||||
|
||||
it("should 'ce-specversion' have the right value", () =>
|
||||
emitBinary(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(cloudevent.specversion).to.equal(response.config.headers["ce-specversion"]);
|
||||
}));
|
||||
|
||||
it("should 'ce-source' have the right value", () =>
|
||||
emitBinary(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(cloudevent.source).to.equal(response.config.headers["ce-source"]);
|
||||
}));
|
||||
|
||||
it("should 'ce-id' have the right value", () =>
|
||||
emitBinary(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(cloudevent.id).to.equal(response.config.headers["ce-id"]);
|
||||
}));
|
||||
|
||||
it("should 'ce-time' have the right value", () =>
|
||||
emitBinary(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(cloudevent.time).to.equal(response.config.headers["ce-time"]);
|
||||
}));
|
||||
|
||||
it("should 'ce-dataschema' have the right value", () =>
|
||||
emitBinary(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(cloudevent.dataschema).to.equal(response.config.headers["ce-dataschema"]);
|
||||
}));
|
||||
|
||||
it(`should 'ce-${ext1Name}' have the right value`, () =>
|
||||
emitBinary(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(cloudevent[ext1Name]).to.equal(response.config.headers[`ce-${ext1Name}`]);
|
||||
}));
|
||||
|
||||
it(`should 'ce-${ext2Name}' have the right value`, () =>
|
||||
emitBinary(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(cloudevent[ext2Name]).to.equal(response.config.headers[`ce-${ext2Name}`]);
|
||||
}));
|
||||
|
||||
it("should 'ce-subject' have the right value", () =>
|
||||
emitBinary(cloudevent, httpcfg).then((response: AxiosResponse) => {
|
||||
expect(cloudevent.subject).to.equal(response.config.headers["ce-subject"]);
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,225 @@
|
|||
import "mocha";
|
||||
import { expect } from "chai";
|
||||
import nock from "nock";
|
||||
import CONSTANTS from "../src/constants";
|
||||
|
||||
const DEFAULT_CE_CONTENT_TYPE = CONSTANTS.DEFAULT_CE_CONTENT_TYPE;
|
||||
|
||||
import { CloudEvent, Version, Emitter, Protocol, headersFor } from "../src";
|
||||
import { AxiosResponse } from "axios";
|
||||
|
||||
const receiver = "https://cloudevents.io/";
|
||||
const type = "com.example.test";
|
||||
const source = "urn:event:from:myapi/resource/123";
|
||||
const ext1Name = "lunch";
|
||||
const ext1Value = "tacos";
|
||||
const ext2Name = "supper";
|
||||
const ext2Value = "sushi";
|
||||
|
||||
const data = {
|
||||
lunchBreak: "noon",
|
||||
};
|
||||
|
||||
describe("HTTP Transport Binding Emitter for CloudEvents", () => {
|
||||
beforeEach(() => {
|
||||
nock(receiver)
|
||||
.post("/")
|
||||
|
||||
.reply(function (uri: string, body: nock.Body) {
|
||||
// return the request body and the headers so they can be
|
||||
// examined in the test
|
||||
if (typeof body === "string") {
|
||||
body = JSON.parse(body);
|
||||
}
|
||||
const returnBody = { ...(body as Record<string, unknown>), ...this.req.headers };
|
||||
return [201, returnBody];
|
||||
});
|
||||
});
|
||||
|
||||
describe("V1", () => {
|
||||
const emitter = new Emitter({ url: receiver });
|
||||
const event = new CloudEvent({
|
||||
type,
|
||||
source,
|
||||
time: new Date(),
|
||||
data,
|
||||
[ext1Name]: ext1Value,
|
||||
[ext2Name]: ext2Value,
|
||||
});
|
||||
|
||||
it("Sends a binary 1.0 CloudEvent by default", () => {
|
||||
emitter
|
||||
.send(event)
|
||||
.then((response: AxiosResponse) => {
|
||||
// A binary message will have a ce-id header
|
||||
expect(response.data[CONSTANTS.CE_HEADERS.ID]).to.equal(event.id);
|
||||
expect(response.data[CONSTANTS.CE_HEADERS.SPEC_VERSION]).to.equal(Version.V1);
|
||||
// A binary message will have a request body for the data
|
||||
expect(response.data.lunchBreak).to.equal(data.lunchBreak);
|
||||
// Ensure extensions are handled properly
|
||||
expect(response.data[`${CONSTANTS.EXTENSIONS_PREFIX}${ext1Name}`]).to.equal(ext1Value);
|
||||
expect(response.data[`${CONSTANTS.EXTENSIONS_PREFIX}${ext2Name}`]).to.equal(ext2Value);
|
||||
})
|
||||
.catch(expect.fail);
|
||||
});
|
||||
|
||||
it("Provides the HTTP headers for a binary event", () => {
|
||||
const headers = headersFor(event);
|
||||
expect(headers[CONSTANTS.CE_HEADERS.TYPE]).to.equal(event.type);
|
||||
expect(headers[CONSTANTS.CE_HEADERS.SPEC_VERSION]).to.equal(event.specversion);
|
||||
expect(headers[CONSTANTS.CE_HEADERS.SOURCE]).to.equal(event.source);
|
||||
expect(headers[CONSTANTS.CE_HEADERS.ID]).to.equal(event.id);
|
||||
expect(headers[CONSTANTS.CE_HEADERS.TIME]).to.equal(event.time);
|
||||
});
|
||||
|
||||
it("Sends a binary CloudEvent with Custom Headers", () => {
|
||||
emitter
|
||||
.send(event, { headers: { customheader: "value" } })
|
||||
.then((response: { data: { [k: string]: string } }) => {
|
||||
// A binary message will have a ce-id header
|
||||
expect(response.data.customheader).to.equal("value");
|
||||
expect(response.data[CONSTANTS.CE_HEADERS.ID]).to.equal(event.id);
|
||||
expect(response.data[CONSTANTS.CE_HEADERS.SPEC_VERSION]).to.equal(Version.V1);
|
||||
// A binary message will have a request body for the data
|
||||
expect(response.data.lunchBreak).to.equal(data.lunchBreak);
|
||||
})
|
||||
.catch(expect.fail);
|
||||
});
|
||||
|
||||
it("Sends a structured 1.0 CloudEvent if specified", () => {
|
||||
emitter
|
||||
.send(event, { protocol: Protocol.HTTPStructured })
|
||||
.then((response: { data: { [k: string]: string | Record<string, string>; data: { lunchBreak: string } } }) => {
|
||||
// A structured message will have a cloud event content type
|
||||
expect(response.data["content-type"]).to.equal(DEFAULT_CE_CONTENT_TYPE);
|
||||
// Ensure other CE headers don't exist - just testing for ID
|
||||
expect(response.data[CONSTANTS.CE_HEADERS.ID]).to.equal(undefined);
|
||||
// The spec version would have been specified in the body
|
||||
expect(response.data.specversion).to.equal(Version.V1);
|
||||
expect(response.data.data.lunchBreak).to.equal(data.lunchBreak);
|
||||
// Ensure extensions are handled properly
|
||||
expect(response.data[ext1Name]).to.equal(ext1Value);
|
||||
expect(response.data[ext2Name]).to.equal(ext2Value);
|
||||
})
|
||||
.catch(expect.fail);
|
||||
});
|
||||
|
||||
it("Sends to an alternate URL if specified", () => {
|
||||
nock(receiver)
|
||||
.post("/alternate")
|
||||
.reply(function (uri, requestBody: nock.Body) {
|
||||
// return the request body and the headers so they can be
|
||||
// examined in the test
|
||||
if (typeof requestBody === "string") {
|
||||
requestBody = JSON.parse(requestBody);
|
||||
}
|
||||
const returnBody = { ...(requestBody as Record<string, unknown>), ...this.req.headers };
|
||||
return [201, returnBody];
|
||||
});
|
||||
|
||||
emitter
|
||||
.send(event, { protocol: Protocol.HTTPStructured, url: `${receiver}alternate` })
|
||||
.then((response: AxiosResponse) => {
|
||||
// A structured message will have a cloud event content type
|
||||
expect(response.data["content-type"]).to.equal(DEFAULT_CE_CONTENT_TYPE);
|
||||
// Ensure other CE headers don't exist - just testing for ID
|
||||
expect(response.data[CONSTANTS.CE_HEADERS.ID]).to.equal(undefined);
|
||||
// The spec version would have been specified in the body
|
||||
expect(response.data.specversion).to.equal(Version.V1);
|
||||
expect(response.data.data.lunchBreak).to.equal(data.lunchBreak);
|
||||
})
|
||||
.catch(expect.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe("V03", () => {
|
||||
const emitter = new Emitter({ url: receiver });
|
||||
const event = new CloudEvent({
|
||||
specversion: Version.V03,
|
||||
type,
|
||||
source,
|
||||
time: new Date(),
|
||||
data,
|
||||
[ext1Name]: ext1Value,
|
||||
[ext2Name]: ext2Value,
|
||||
});
|
||||
|
||||
it("Sends a binary 0.3 CloudEvent", () => {
|
||||
emitter
|
||||
.send(event)
|
||||
.then((response: AxiosResponse) => {
|
||||
// A binary message will have a ce-id header
|
||||
expect(response.data[CONSTANTS.CE_HEADERS.ID]).to.equal(event.id);
|
||||
expect(response.data[CONSTANTS.CE_HEADERS.SPEC_VERSION]).to.equal(Version.V03);
|
||||
// A binary message will have a request body for the data
|
||||
expect(response.data.lunchBreak).to.equal(data.lunchBreak);
|
||||
// Ensure extensions are handled properly
|
||||
expect(response.data[`${CONSTANTS.EXTENSIONS_PREFIX}${ext1Name}`]).to.equal(ext1Value);
|
||||
expect(response.data[`${CONSTANTS.EXTENSIONS_PREFIX}${ext2Name}`]).to.equal(ext2Value);
|
||||
})
|
||||
.catch(expect.fail);
|
||||
});
|
||||
|
||||
it("Provides the HTTP headers for a binary event", () => {
|
||||
const headers = headersFor(event);
|
||||
expect(headers[CONSTANTS.CE_HEADERS.TYPE]).to.equal(event.type);
|
||||
expect(headers[CONSTANTS.CE_HEADERS.SPEC_VERSION]).to.equal(event.specversion);
|
||||
expect(headers[CONSTANTS.CE_HEADERS.SOURCE]).to.equal(event.source);
|
||||
expect(headers[CONSTANTS.CE_HEADERS.ID]).to.equal(event.id);
|
||||
expect(headers[CONSTANTS.CE_HEADERS.TIME]).to.equal(event.time);
|
||||
});
|
||||
|
||||
it("Sends a structured 0.3 CloudEvent if specified", () => {
|
||||
emitter
|
||||
.send(event, { protocol: Protocol.HTTPStructured })
|
||||
.then(
|
||||
(response: {
|
||||
data: { [k: string]: string | Record<string, string>; specversion: string; data: { lunchBreak: string } };
|
||||
}) => {
|
||||
// A structured message will have a cloud event content type
|
||||
expect(response.data["content-type"]).to.equal(DEFAULT_CE_CONTENT_TYPE);
|
||||
// Ensure other CE headers don't exist - just testing for ID
|
||||
expect(response.data[CONSTANTS.CE_HEADERS.ID]).to.equal(undefined);
|
||||
// The spec version would have been specified in the body
|
||||
expect(response.data.specversion).to.equal(Version.V03);
|
||||
expect(response.data.data.lunchBreak).to.equal(data.lunchBreak);
|
||||
// Ensure extensions are handled properly
|
||||
expect(response.data[ext1Name]).to.equal(ext1Value);
|
||||
expect(response.data[ext2Name]).to.equal(ext2Value);
|
||||
},
|
||||
)
|
||||
.catch(expect.fail);
|
||||
});
|
||||
|
||||
it("Sends to an alternate URL if specified", () => {
|
||||
nock(receiver)
|
||||
.post("/alternate")
|
||||
.reply(function (uri, requestBody: nock.Body) {
|
||||
// return the request body and the headers so they can be
|
||||
// examined in the test
|
||||
if (typeof requestBody === "string") {
|
||||
requestBody = JSON.parse(requestBody);
|
||||
}
|
||||
const returnBody = { ...(requestBody as Record<string, unknown>), ...this.req.headers };
|
||||
return [201, returnBody];
|
||||
});
|
||||
|
||||
emitter
|
||||
.send(event, { protocol: Protocol.HTTPStructured, url: `${receiver}alternate` })
|
||||
.then(
|
||||
(response: {
|
||||
data: { specversion: string; data: { lunchBreak: string }; [k: string]: string | Record<string, string> };
|
||||
}) => {
|
||||
// A structured message will have a cloud event content type
|
||||
expect(response.data["content-type"]).to.equal(DEFAULT_CE_CONTENT_TYPE);
|
||||
// Ensure other CE headers don't exist - just testing for ID
|
||||
expect(response.data[CONSTANTS.CE_HEADERS.ID]).to.equal(undefined);
|
||||
// The spec version would have been specified in the body
|
||||
expect(response.data.specversion).to.equal(Version.V03);
|
||||
expect(response.data.data.lunchBreak).to.equal(data.lunchBreak);
|
||||
},
|
||||
)
|
||||
.catch(expect.fail);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,24 +1,14 @@
|
|||
import "mocha";
|
||||
import { expect } from "chai";
|
||||
import { CloudEvent, HTTPReceiver } from "..";
|
||||
import { CloudEventV1 } from "../lib/v1";
|
||||
const {
|
||||
HEADER_CONTENT_TYPE,
|
||||
DEFAULT_CONTENT_TYPE,
|
||||
MIME_CE_JSON,
|
||||
DEFAULT_SPEC_VERSION_HEADER,
|
||||
BINARY_HEADERS_03,
|
||||
BINARY_HEADERS_1
|
||||
} = require("../lib/bindings/http/constants");
|
||||
const ValidationError = require("../lib/bindings/http/validation/validation_error.js");
|
||||
import { CloudEvent, Receiver, ValidationError } from "../src";
|
||||
import { CloudEventV1 } from "../src/event/v1";
|
||||
|
||||
const receiver = new HTTPReceiver();
|
||||
const receiver = new Receiver();
|
||||
const id = "1234";
|
||||
const type = "org.cncf.cloudevents.test";
|
||||
const source = "urn:event:from:myapi/resourse/123";
|
||||
const data = {
|
||||
lunch: "sushi"
|
||||
};
|
||||
const structuredHeaders = { "content-type": "application/cloudevents+json" };
|
||||
const data = { lunch: "sushi" };
|
||||
|
||||
describe("HTTP Transport Binding Receiver for CloudEvents", () => {
|
||||
describe("HTTP CloudEvent format detection", () => {
|
||||
|
@ -29,11 +19,38 @@ describe("HTTP Transport Binding Receiver for CloudEvents", () => {
|
|||
type,
|
||||
source,
|
||||
data,
|
||||
specversion
|
||||
specversion,
|
||||
};
|
||||
|
||||
expect(receiver.accept.bind(receiver, {}, payload))
|
||||
.to.throw(ValidationError, "no cloud event detected");
|
||||
expect(receiver.accept.bind(receiver, {}, payload)).to.throw(ValidationError, "no cloud event detected");
|
||||
});
|
||||
|
||||
it("Converts the JSON body of a binary event to an Object", () => {
|
||||
const binaryHeaders = {
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"ce-specversion": specversion,
|
||||
"ce-id": id,
|
||||
"ce-type": type,
|
||||
"ce-source": source,
|
||||
};
|
||||
|
||||
const event: CloudEvent = receiver.accept(binaryHeaders, data);
|
||||
expect(typeof event.data).to.equal("object");
|
||||
expect((event.data as Record<string, string>).lunch).to.equal("sushi");
|
||||
});
|
||||
|
||||
it("Converts the JSON body of a structured event to an Object", () => {
|
||||
const payload = {
|
||||
id,
|
||||
type,
|
||||
source,
|
||||
data,
|
||||
specversion,
|
||||
};
|
||||
|
||||
const event = receiver.accept(structuredHeaders, payload);
|
||||
expect(typeof event.data).to.equal("object");
|
||||
expect((event.data as Record<string, string>).lunch).to.equal("sushi");
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -46,27 +63,23 @@ describe("HTTP Transport Binding Receiver for CloudEvents", () => {
|
|||
type,
|
||||
source,
|
||||
data,
|
||||
specversion
|
||||
specversion,
|
||||
};
|
||||
|
||||
const headers = {
|
||||
[HEADER_CONTENT_TYPE]: MIME_CE_JSON
|
||||
};
|
||||
|
||||
const event = receiver.accept(headers, payload);
|
||||
const event = receiver.accept(structuredHeaders, payload);
|
||||
validateEvent(event, specversion);
|
||||
});
|
||||
|
||||
it("Binary data returns a CloudEvent", () => {
|
||||
const headers = {
|
||||
[HEADER_CONTENT_TYPE]: DEFAULT_CONTENT_TYPE,
|
||||
[DEFAULT_SPEC_VERSION_HEADER]: specversion,
|
||||
[BINARY_HEADERS_1.ID]: id,
|
||||
[BINARY_HEADERS_1.TYPE]: type,
|
||||
[BINARY_HEADERS_1.SOURCE]: source
|
||||
const binaryHeaders = {
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"ce-specversion": specversion,
|
||||
"ce-id": id,
|
||||
"ce-type": type,
|
||||
"ce-source": source,
|
||||
};
|
||||
|
||||
const event = receiver.accept(headers, data);
|
||||
const event = receiver.accept(binaryHeaders, data);
|
||||
validateEvent(event, specversion);
|
||||
});
|
||||
});
|
||||
|
@ -80,27 +93,23 @@ describe("HTTP Transport Binding Receiver for CloudEvents", () => {
|
|||
type,
|
||||
source,
|
||||
data,
|
||||
specversion
|
||||
specversion,
|
||||
};
|
||||
|
||||
const headers = {
|
||||
[HEADER_CONTENT_TYPE]: MIME_CE_JSON
|
||||
};
|
||||
|
||||
const event = receiver.accept(headers, payload);
|
||||
const event = receiver.accept(structuredHeaders, payload);
|
||||
validateEvent(event, specversion);
|
||||
});
|
||||
|
||||
it("Binary data returns a CloudEvent", () => {
|
||||
const headers = {
|
||||
[HEADER_CONTENT_TYPE]: DEFAULT_CONTENT_TYPE,
|
||||
[DEFAULT_SPEC_VERSION_HEADER]: specversion,
|
||||
[BINARY_HEADERS_03.ID]: id,
|
||||
[BINARY_HEADERS_03.TYPE]: type,
|
||||
[BINARY_HEADERS_03.SOURCE]: source
|
||||
const binaryHeaders = {
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"ce-specversion": specversion,
|
||||
"ce-id": id,
|
||||
"ce-type": type,
|
||||
"ce-source": source,
|
||||
};
|
||||
|
||||
const event = receiver.accept(headers, data);
|
||||
const event = receiver.accept(binaryHeaders, data);
|
||||
validateEvent(event, specversion);
|
||||
});
|
||||
});
|
||||
|
@ -109,8 +118,7 @@ describe("HTTP Transport Binding Receiver for CloudEvents", () => {
|
|||
const specversion = "1.0";
|
||||
const id = "partition:1/offset:23";
|
||||
const type = "dev.knative.kafka.event";
|
||||
const source =
|
||||
"/apis/v1/namespaces/kafka/kafkasources/kafka-source#knative-demo-topic";
|
||||
const source = "/apis/v1/namespaces/kafka/kafkasources/kafka-source#knative-demo-topic";
|
||||
|
||||
it("Should be parsable", () => {
|
||||
const headers = {
|
||||
|
@ -129,7 +137,7 @@ describe("HTTP Transport Binding Receiver for CloudEvents", () => {
|
|||
"x-envoy-expected-rq-timeout-ms": "600000",
|
||||
"x-forwarded-for": "10.131.0.72, 10.128.2.99",
|
||||
"x-forwarded-proto": "http",
|
||||
"x-request-id": "d3649c1b-a968-40bf-a9da-3e853abc0c8b"
|
||||
"x-request-id": "d3649c1b-a968-40bf-a9da-3e853abc0c8b",
|
||||
};
|
||||
const event = receiver.accept(headers, data);
|
||||
expect(event instanceof CloudEvent).to.equal(true);
|
|
@ -1,6 +1,8 @@
|
|||
const expect = require("chai").expect;
|
||||
const Parser = require("../../../lib/formats/json/parser.js");
|
||||
const ValidationError = require("../../../lib/bindings/http/validation/validation_error.js");
|
||||
import "mocha";
|
||||
import { expect } from "chai";
|
||||
|
||||
import { JSONParser as Parser } from "../src/parsers/";
|
||||
import { ValidationError } from "../src/";
|
||||
|
||||
describe("JSON Event Format Parser", () => {
|
||||
it("Throw error when payload is an integer", () => {
|
||||
|
@ -8,18 +10,20 @@ describe("JSON Event Format Parser", () => {
|
|||
const payload = 83;
|
||||
const parser = new Parser();
|
||||
|
||||
// act and assert
|
||||
expect(parser.parse.bind(parser, payload))
|
||||
.to.throw(ValidationError, "invalid payload type, allowed are: string or object");
|
||||
expect(parser.parse.bind(parser, (payload as unknown) as string)).to.throw(
|
||||
ValidationError,
|
||||
"invalid payload type, allowed are: string or object",
|
||||
);
|
||||
});
|
||||
|
||||
it("Throw error when payload is null", () => {
|
||||
// setup
|
||||
const payload = null;
|
||||
const parser = new Parser();
|
||||
|
||||
// act and assert
|
||||
expect(parser.parse.bind(parser, payload)).to.throw(ValidationError, "null or undefined payload");
|
||||
expect(parser.parse.bind(parser, (payload as unknown) as string)).to.throw(
|
||||
ValidationError,
|
||||
"null or undefined payload",
|
||||
);
|
||||
});
|
||||
|
||||
it("Throw error when payload is undefined", () => {
|
||||
|
@ -35,9 +39,10 @@ describe("JSON Event Format Parser", () => {
|
|||
const payload = 8.3;
|
||||
const parser = new Parser();
|
||||
|
||||
// act and assert
|
||||
expect(parser.parse.bind(parser, payload))
|
||||
.to.throw(ValidationError, "invalid payload type, allowed are: string or object");
|
||||
expect(parser.parse.bind(parser, (payload as unknown) as string)).to.throw(
|
||||
ValidationError,
|
||||
"invalid payload type, allowed are: string or object",
|
||||
);
|
||||
});
|
||||
|
||||
it("Throw error when payload is an invalid JSON", () => {
|
||||
|
@ -51,6 +56,7 @@ describe("JSON Event Format Parser", () => {
|
|||
|
||||
it("Must accept when the payload is a string well formed as JSON", () => {
|
||||
// setup
|
||||
// eslint-disable-next-line prettier/prettier
|
||||
const payload = "{\"much\" : \"wow\"}";
|
||||
const parser = new Parser();
|
||||
|
|
@ -0,0 +1,434 @@
|
|||
import "mocha";
|
||||
import { expect } from "chai";
|
||||
|
||||
import { CloudEvent, ValidationError, Version } from "../src";
|
||||
import { BinaryHTTPReceiver } from "../src/transport/http/binary_receiver";
|
||||
import CONSTANTS from "../src/constants";
|
||||
|
||||
const receiver = new BinaryHTTPReceiver(Version.V03);
|
||||
|
||||
describe("HTTP Transport Binding Binary Receiver for CloudEvents v0.3", () => {
|
||||
describe("Check", () => {
|
||||
it("Throw error when payload arg is null or undefined", () => {
|
||||
// setup
|
||||
const payload = undefined;
|
||||
const attributes = {};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.parse.bind(receiver, (payload as unknown) as string, attributes)).to.throw(
|
||||
ValidationError,
|
||||
"payload is null or undefined",
|
||||
);
|
||||
});
|
||||
|
||||
it("Throw error when attributes arg is null or undefined", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = undefined;
|
||||
|
||||
expect(receiver.parse.bind(receiver, payload, (attributes as unknown) as string)).to.throw(
|
||||
ValidationError,
|
||||
"headers is null or undefined",
|
||||
);
|
||||
});
|
||||
|
||||
it("Throw error when payload is not an object or string", () => {
|
||||
// setup
|
||||
const payload = 1.2;
|
||||
const attributes = {};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.parse.bind(receiver, payload, attributes)).to.throw(
|
||||
ValidationError,
|
||||
"payload must be an object or a string",
|
||||
);
|
||||
});
|
||||
|
||||
it("Throw error when headers has no 'ce-type'", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V03,
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: "source",
|
||||
[CONSTANTS.CE_HEADERS.ID]: "id",
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: "application/json",
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.parse.bind(receiver, payload, attributes)).to.throw(
|
||||
ValidationError,
|
||||
"header 'ce-type' not found",
|
||||
);
|
||||
});
|
||||
|
||||
it("Throw error when headers has no 'ce-specversion'", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: "type",
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: "source",
|
||||
[CONSTANTS.CE_HEADERS.ID]: "id",
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: "application/json",
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.parse.bind(receiver, payload, attributes)).to.throw(
|
||||
ValidationError,
|
||||
"header 'ce-specversion' not found",
|
||||
);
|
||||
});
|
||||
|
||||
it("Throw error when headers has no 'ce-source'", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: "type",
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V03,
|
||||
[CONSTANTS.CE_HEADERS.ID]: "id",
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: "application/json",
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.parse.bind(receiver, payload, attributes)).to.throw(
|
||||
ValidationError,
|
||||
"header 'ce-source' not found",
|
||||
);
|
||||
});
|
||||
|
||||
it("Throw error when headers has no 'ce-id'", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: "type",
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V03,
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: "source",
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: "application/json",
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.parse.bind(receiver, payload, attributes)).to.throw(ValidationError, "header 'ce-id' not found");
|
||||
});
|
||||
|
||||
it("Throw error when spec is not 0.3", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: "type",
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: "0.2",
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: "source",
|
||||
[CONSTANTS.CE_HEADERS.ID]: "id",
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: "application/json",
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.parse.bind(receiver, payload, attributes)).to.throw(ValidationError, "invalid spec version");
|
||||
});
|
||||
|
||||
it("Throw error when the content-type is invalid", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: "type",
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V03,
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: "source",
|
||||
[CONSTANTS.CE_HEADERS.ID]: "id",
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: "text/html",
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.parse.bind(receiver, payload, attributes)).to.throw(ValidationError, "invalid content type");
|
||||
});
|
||||
|
||||
it("No error when all required headers are in place", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: "type",
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V03,
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: "source",
|
||||
[CONSTANTS.CE_HEADERS.ID]: "id",
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: "application/json",
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.parse.bind(receiver, payload, attributes)).to.not.throw();
|
||||
});
|
||||
|
||||
it("No error when content-type is unspecified", () => {
|
||||
const payload = {};
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: "type",
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V03,
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: "source",
|
||||
[CONSTANTS.CE_HEADERS.ID]: "id",
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.parse.bind(receiver, payload, attributes)).to.not.throw();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Parse", () => {
|
||||
it("CloudEvent contains 'type'", () => {
|
||||
// setup
|
||||
const payload = {
|
||||
data: "dataString",
|
||||
};
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: "type",
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V03,
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: "source",
|
||||
[CONSTANTS.CE_HEADERS.ID]: "id",
|
||||
[CONSTANTS.CE_HEADERS.TIME]: "2019-06-16T11:42:00Z",
|
||||
[CONSTANTS.BINARY_HEADERS_03.SCHEMA_URL]: "http://schema.registry/v1",
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: "application/json",
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.type).to.equal("type");
|
||||
});
|
||||
|
||||
it("CloudEvent contains 'specversion'", () => {
|
||||
// setup
|
||||
const payload = {
|
||||
data: "dataString",
|
||||
};
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: "type",
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V03,
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: "source",
|
||||
[CONSTANTS.CE_HEADERS.ID]: "id",
|
||||
[CONSTANTS.CE_HEADERS.TIME]: "2019-06-16T11:42:00Z",
|
||||
[CONSTANTS.BINARY_HEADERS_03.SCHEMA_URL]: "http://schema.registry/v1",
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: "application/json",
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.specversion).to.equal(Version.V03);
|
||||
});
|
||||
|
||||
it("CloudEvent contains 'source'", () => {
|
||||
// setup
|
||||
const payload = {
|
||||
data: "dataString",
|
||||
};
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: "type",
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V03,
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: "/source",
|
||||
[CONSTANTS.CE_HEADERS.ID]: "id",
|
||||
[CONSTANTS.CE_HEADERS.TIME]: "2019-06-16T11:42:00Z",
|
||||
[CONSTANTS.BINARY_HEADERS_03.SCHEMA_URL]: "http://schema.registry/v1",
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: "application/json",
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.source).to.equal("/source");
|
||||
});
|
||||
|
||||
it("CloudEvent contains 'id'", () => {
|
||||
// setup
|
||||
const payload = {
|
||||
data: "dataString",
|
||||
};
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: "type",
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V03,
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: "/source",
|
||||
[CONSTANTS.CE_HEADERS.ID]: "id",
|
||||
[CONSTANTS.CE_HEADERS.TIME]: "2019-06-16T11:42:00Z",
|
||||
[CONSTANTS.BINARY_HEADERS_03.SCHEMA_URL]: "http://schema.registry/v1",
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: "application/json",
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.id).to.equal("id");
|
||||
});
|
||||
|
||||
it("CloudEvent contains 'time'", () => {
|
||||
// setup
|
||||
const payload = {
|
||||
data: "dataString",
|
||||
};
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: "type",
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V03,
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: "/source",
|
||||
[CONSTANTS.CE_HEADERS.ID]: "id",
|
||||
[CONSTANTS.CE_HEADERS.TIME]: "2019-06-16T11:42:00Z",
|
||||
[CONSTANTS.BINARY_HEADERS_03.SCHEMA_URL]: "http://schema.registry/v1",
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: "application/json",
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.time).to.equal("2019-06-16T11:42:00.000Z");
|
||||
});
|
||||
|
||||
it("CloudEvent contains 'schemaurl'", () => {
|
||||
// setup
|
||||
const payload = {
|
||||
data: "dataString",
|
||||
};
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: "type",
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V03,
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: "/source",
|
||||
[CONSTANTS.CE_HEADERS.ID]: "id",
|
||||
[CONSTANTS.CE_HEADERS.TIME]: "2019-06-16T11:42:00Z",
|
||||
[CONSTANTS.BINARY_HEADERS_03.SCHEMA_URL]: "http://schema.registry/v1",
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: "application/json",
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.schemaurl).to.equal("http://schema.registry/v1");
|
||||
});
|
||||
|
||||
it("CloudEvent contains 'datacontenttype' (application/json)", () => {
|
||||
// setup
|
||||
const payload = {
|
||||
data: "dataString",
|
||||
};
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: "type",
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V03,
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: "/source",
|
||||
[CONSTANTS.CE_HEADERS.ID]: "id",
|
||||
[CONSTANTS.CE_HEADERS.TIME]: "2019-06-16T11:42:00Z",
|
||||
[CONSTANTS.BINARY_HEADERS_03.SCHEMA_URL]: "http://schema.registry/v1",
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: "application/json",
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.datacontenttype).to.equal("application/json");
|
||||
});
|
||||
|
||||
it("CloudEvent contains 'datacontenttype' (application/octet-stream)", () => {
|
||||
// setup
|
||||
const payload = "The payload is binary data";
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: "type",
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V03,
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: "/source",
|
||||
[CONSTANTS.CE_HEADERS.ID]: "id",
|
||||
[CONSTANTS.CE_HEADERS.TIME]: "2019-06-16T11:42:00Z",
|
||||
[CONSTANTS.BINARY_HEADERS_03.SCHEMA_URL]: "http://schema.registry/v1",
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: "application/octet-stream",
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.datacontenttype).to.equal("application/octet-stream");
|
||||
});
|
||||
|
||||
it("CloudEvent contains 'data' (application/json)", () => {
|
||||
// setup
|
||||
const payload = {
|
||||
data: "dataString",
|
||||
};
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: "type",
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V03,
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: "/source",
|
||||
[CONSTANTS.CE_HEADERS.ID]: "id",
|
||||
[CONSTANTS.CE_HEADERS.TIME]: "2019-06-16T11:42:00Z",
|
||||
[CONSTANTS.BINARY_HEADERS_03.SCHEMA_URL]: "http://schema.registry/v1",
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: "application/json",
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.data).to.deep.equal(payload);
|
||||
});
|
||||
|
||||
it("CloudEvent contains 'data' (application/octet-stream)", () => {
|
||||
// setup
|
||||
const payload = "The payload is binary data";
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: "type",
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V03,
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: "/source",
|
||||
[CONSTANTS.CE_HEADERS.ID]: "id",
|
||||
[CONSTANTS.CE_HEADERS.TIME]: "2019-06-16T11:42:00Z",
|
||||
[CONSTANTS.BINARY_HEADERS_03.SCHEMA_URL]: "http://schema.registry/v1",
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: "application/octet-stream",
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.data).to.deep.equal(payload);
|
||||
});
|
||||
|
||||
it("No error when all attributes are in place", () => {
|
||||
// setup
|
||||
const payload = {
|
||||
data: "dataString",
|
||||
};
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: "type",
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V03,
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: "source",
|
||||
[CONSTANTS.CE_HEADERS.ID]: "id",
|
||||
[CONSTANTS.CE_HEADERS.TIME]: "2019-06-16T11:42:00Z",
|
||||
[CONSTANTS.BINARY_HEADERS_03.SCHEMA_URL]: "http://schema.registry/v1",
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: "application/json",
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual).to.be.an.instanceof(CloudEvent);
|
||||
});
|
||||
|
||||
it("Should accept 'extension1'", () => {
|
||||
// setup
|
||||
const extension1 = "mycuston-ext1";
|
||||
const payload = {
|
||||
data: "dataString",
|
||||
};
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: "type",
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V03,
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: "source",
|
||||
[CONSTANTS.CE_HEADERS.ID]: "id",
|
||||
[CONSTANTS.CE_HEADERS.TIME]: "2019-06-16T11:42:00Z",
|
||||
[CONSTANTS.BINARY_HEADERS_03.SCHEMA_URL]: "http://schema.registry/v1",
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: "application/json",
|
||||
[`${[CONSTANTS.EXTENSIONS_PREFIX]}extension1`]: extension1,
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.extension1).to.equal(extension1);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,460 @@
|
|||
import "mocha";
|
||||
import { expect } from "chai";
|
||||
|
||||
import { CloudEvent, ValidationError, Version } from "../src";
|
||||
import { asBase64 } from "../src/event/validation";
|
||||
import { BinaryHTTPReceiver } from "../src/transport/http/binary_receiver";
|
||||
import CONSTANTS from "../src/constants";
|
||||
|
||||
const receiver = new BinaryHTTPReceiver(Version.V1);
|
||||
|
||||
describe("HTTP Transport Binding Binary Receiver for CloudEvents v1.0", () => {
|
||||
describe("Check", () => {
|
||||
it("Throw error when payload arg is null or undefined", () => {
|
||||
// setup
|
||||
const payload = null;
|
||||
const attributes = {};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.parse.bind(receiver, (payload as unknown) as string, attributes)).to.throw(
|
||||
ValidationError,
|
||||
"payload is null or undefined",
|
||||
);
|
||||
});
|
||||
|
||||
it("Throw error when attributes arg is null or undefined", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = undefined;
|
||||
|
||||
expect(receiver.parse.bind(receiver, payload, (attributes as unknown) as string)).to.throw(
|
||||
ValidationError,
|
||||
"headers is null or undefined",
|
||||
);
|
||||
});
|
||||
|
||||
it("Throw error when payload is not an object or string", () => {
|
||||
// setup
|
||||
const payload = 1.2;
|
||||
const attributes = {};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.parse.bind(receiver, payload, attributes)).to.throw(
|
||||
ValidationError,
|
||||
"payload must be an object or a string",
|
||||
);
|
||||
});
|
||||
|
||||
it("Throw error when headers has no 'ce-type'", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V1,
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: "source",
|
||||
[CONSTANTS.CE_HEADERS.ID]: "id",
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: "application/json",
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.parse.bind(receiver, payload, attributes)).to.throw(
|
||||
ValidationError,
|
||||
"header 'ce-type' not found",
|
||||
);
|
||||
});
|
||||
|
||||
it("Throw error when headers has no 'ce-specversion'", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: "type",
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: "source",
|
||||
[CONSTANTS.CE_HEADERS.ID]: "id",
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: "application/json",
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.parse.bind(receiver, payload, attributes)).to.throw(
|
||||
ValidationError,
|
||||
"header 'ce-specversion' not found",
|
||||
);
|
||||
});
|
||||
|
||||
it("Throw error when headers has no 'ce-source'", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: "type",
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V1,
|
||||
[CONSTANTS.CE_HEADERS.ID]: "id",
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: "application/json",
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.parse.bind(receiver, payload, attributes)).to.throw(
|
||||
ValidationError,
|
||||
"header 'ce-source' not found",
|
||||
);
|
||||
});
|
||||
|
||||
it("Throw error when headers has no 'ce-id'", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: "type",
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V1,
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: "source",
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: "application/json",
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.parse.bind(receiver, payload, attributes)).to.throw(ValidationError, "header 'ce-id' not found");
|
||||
});
|
||||
|
||||
it("Throw error when spec is not 1.0", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: "type",
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: "0.2",
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: "source",
|
||||
[CONSTANTS.CE_HEADERS.ID]: "id",
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: "application/json",
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.parse.bind(receiver, payload, attributes)).to.throw(ValidationError, "invalid spec version");
|
||||
});
|
||||
|
||||
it("Throw error when the content-type is invalid", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: "type",
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V1,
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: "source",
|
||||
[CONSTANTS.CE_HEADERS.ID]: "id",
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: "text/html",
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.parse.bind(receiver, payload, attributes)).to.throw(ValidationError, "invalid content type");
|
||||
});
|
||||
|
||||
it("No error when content-type is unspecified", () => {
|
||||
const payload = {};
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: "type",
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V1,
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: "source",
|
||||
[CONSTANTS.CE_HEADERS.ID]: "id",
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.parse.bind(receiver, payload, attributes)).to.not.throw();
|
||||
});
|
||||
|
||||
it("No error when all required headers are in place", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: "type",
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V1,
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: "source",
|
||||
[CONSTANTS.CE_HEADERS.ID]: "id",
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: "application/json",
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.parse.bind(receiver, payload, attributes)).to.not.throw();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Parse", () => {
|
||||
it("CloudEvent contains 'type'", () => {
|
||||
// setup
|
||||
const payload = {
|
||||
data: "dataString",
|
||||
};
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: "type",
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V1,
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: "source",
|
||||
[CONSTANTS.CE_HEADERS.ID]: "id",
|
||||
[CONSTANTS.CE_HEADERS.TIME]: "2019-06-16T11:42:00Z",
|
||||
[CONSTANTS.BINARY_HEADERS_1.DATA_SCHEMA]: "http://schema.registry/v1",
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: "application/json",
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.type).to.equal("type");
|
||||
});
|
||||
|
||||
it("CloudEvent contains 'specversion'", () => {
|
||||
// setup
|
||||
const payload = {
|
||||
data: "dataString",
|
||||
};
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: "type",
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V1,
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: "source",
|
||||
[CONSTANTS.CE_HEADERS.ID]: "id",
|
||||
[CONSTANTS.CE_HEADERS.TIME]: "2019-06-16T11:42:00Z",
|
||||
[CONSTANTS.BINARY_HEADERS_1.DATA_SCHEMA]: "http://schema.registry/v1",
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: "application/json",
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.specversion).to.equal(Version.V1);
|
||||
});
|
||||
|
||||
it("CloudEvent contains 'source'", () => {
|
||||
// setup
|
||||
const payload = {
|
||||
data: "dataString",
|
||||
};
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: "type",
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V1,
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: "/source",
|
||||
[CONSTANTS.CE_HEADERS.ID]: "id",
|
||||
[CONSTANTS.CE_HEADERS.TIME]: "2019-06-16T11:42:00Z",
|
||||
[CONSTANTS.BINARY_HEADERS_1.DATA_SCHEMA]: "http://schema.registry/v1",
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: "application/json",
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.source).to.equal("/source");
|
||||
});
|
||||
|
||||
it("CloudEvent contains 'id'", () => {
|
||||
// setup
|
||||
const payload = {
|
||||
data: "dataString",
|
||||
};
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: "type",
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V1,
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: "/source",
|
||||
[CONSTANTS.CE_HEADERS.ID]: "id",
|
||||
[CONSTANTS.CE_HEADERS.TIME]: "2019-06-16T11:42:00Z",
|
||||
[CONSTANTS.BINARY_HEADERS_1.DATA_SCHEMA]: "http://schema.registry/v1",
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: "application/json",
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.id).to.equal("id");
|
||||
});
|
||||
|
||||
it("CloudEvent contains 'time'", () => {
|
||||
// setup
|
||||
const payload = {
|
||||
data: "dataString",
|
||||
};
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: "type",
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V1,
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: "/source",
|
||||
[CONSTANTS.CE_HEADERS.ID]: "id",
|
||||
[CONSTANTS.CE_HEADERS.TIME]: "2019-06-16T11:42:00Z",
|
||||
[CONSTANTS.BINARY_HEADERS_1.DATA_SCHEMA]: "http://schema.registry/v1",
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: "application/json",
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.time).to.equal("2019-06-16T11:42:00.000Z");
|
||||
});
|
||||
|
||||
it("CloudEvent contains 'dataschema'", () => {
|
||||
// setup
|
||||
const payload = {
|
||||
data: "dataString",
|
||||
};
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: "type",
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V1,
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: "/source",
|
||||
[CONSTANTS.CE_HEADERS.ID]: "id",
|
||||
[CONSTANTS.CE_HEADERS.TIME]: "2019-06-16T11:42:00Z",
|
||||
[CONSTANTS.BINARY_HEADERS_1.DATA_SCHEMA]: "http://schema.registry/v1",
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: "application/json",
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.dataschema).to.equal("http://schema.registry/v1");
|
||||
});
|
||||
|
||||
it("CloudEvent contains 'contenttype' (application/json)", () => {
|
||||
// setup
|
||||
const payload = {
|
||||
data: "dataString",
|
||||
};
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: "type",
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V1,
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: "/source",
|
||||
[CONSTANTS.CE_HEADERS.ID]: "id",
|
||||
[CONSTANTS.CE_HEADERS.TIME]: "2019-06-16T11:42:00Z",
|
||||
[CONSTANTS.BINARY_HEADERS_1.DATA_SCHEMA]: "http://schema.registry/v1",
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: "application/json",
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.datacontenttype).to.equal("application/json");
|
||||
});
|
||||
|
||||
it("CloudEvent contains 'contenttype' (application/octet-stream)", () => {
|
||||
// setup
|
||||
const payload = "The payload is binary data";
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: "type",
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V1,
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: "/source",
|
||||
[CONSTANTS.CE_HEADERS.ID]: "id",
|
||||
[CONSTANTS.CE_HEADERS.TIME]: "2019-06-16T11:42:00Z",
|
||||
[CONSTANTS.BINARY_HEADERS_1.DATA_SCHEMA]: "http://schema.registry/v1",
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: "application/octet-stream",
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.datacontenttype).to.equal("application/octet-stream");
|
||||
});
|
||||
|
||||
it("CloudEvent contains 'data' (application/json)", () => {
|
||||
// setup
|
||||
const payload = {
|
||||
data: "dataString",
|
||||
};
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: "type",
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V1,
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: "/source",
|
||||
[CONSTANTS.CE_HEADERS.ID]: "id",
|
||||
[CONSTANTS.CE_HEADERS.TIME]: "2019-06-16T11:42:00Z",
|
||||
[CONSTANTS.BINARY_HEADERS_1.DATA_SCHEMA]: "http://schema.registry/v1",
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: "application/json",
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.data).to.deep.equal(payload);
|
||||
});
|
||||
|
||||
it("CloudEvent contains 'data' (application/octet-stream)", () => {
|
||||
// setup
|
||||
const payload = "The payload is binary data";
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: "type",
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V1,
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: "/source",
|
||||
[CONSTANTS.CE_HEADERS.ID]: "id",
|
||||
[CONSTANTS.CE_HEADERS.TIME]: "2019-06-16T11:42:00Z",
|
||||
[CONSTANTS.BINARY_HEADERS_1.DATA_SCHEMA]: "http://schema.registry/v1",
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: "application/octet-stream",
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.data).to.deep.equal(payload);
|
||||
});
|
||||
|
||||
it("The content of 'data' is base64 for binary", () => {
|
||||
// setup
|
||||
const expected = {
|
||||
data: "dataString",
|
||||
};
|
||||
const bindata = Uint32Array.from(JSON.stringify(expected) as string, (c) => c.codePointAt(0) as number);
|
||||
const payload = asBase64(bindata);
|
||||
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: "type",
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V1,
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: "/source",
|
||||
[CONSTANTS.CE_HEADERS.ID]: "id",
|
||||
[CONSTANTS.CE_HEADERS.TIME]: "2019-06-16T11:42:00Z",
|
||||
[CONSTANTS.BINARY_HEADERS_1.DATA_SCHEMA]: "http://schema.registry/v1",
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: "application/json",
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.data).to.deep.equal(expected);
|
||||
});
|
||||
|
||||
it("No error when all attributes are in place", () => {
|
||||
// setup
|
||||
const payload = {
|
||||
data: "dataString",
|
||||
};
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: "type",
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V1,
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: "source",
|
||||
[CONSTANTS.CE_HEADERS.ID]: "id",
|
||||
[CONSTANTS.CE_HEADERS.TIME]: "2019-06-16T11:42:00Z",
|
||||
[CONSTANTS.BINARY_HEADERS_1.DATA_SCHEMA]: "http://schema.registry/v1",
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: "application/json",
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual).to.be.an.instanceof(CloudEvent);
|
||||
});
|
||||
|
||||
it("Should accept 'extension1'", () => {
|
||||
// setup
|
||||
const extension1 = "mycustom-ext1";
|
||||
const payload = {
|
||||
data: "dataString",
|
||||
};
|
||||
const attributes = {
|
||||
[CONSTANTS.CE_HEADERS.TYPE]: "type",
|
||||
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V1,
|
||||
[CONSTANTS.CE_HEADERS.SOURCE]: "source",
|
||||
[CONSTANTS.CE_HEADERS.ID]: "id",
|
||||
[CONSTANTS.CE_HEADERS.TIME]: "2019-06-16T11:42:00Z",
|
||||
[CONSTANTS.BINARY_HEADERS_1.DATA_SCHEMA]: "http://schema.registry/v1",
|
||||
[CONSTANTS.HEADER_CONTENT_TYPE]: "application/json",
|
||||
[`${[CONSTANTS.EXTENSIONS_PREFIX]}extension1`]: extension1,
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, attributes);
|
||||
|
||||
// assert
|
||||
expect(actual.extension1).to.equal(extension1);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,205 @@
|
|||
import "mocha";
|
||||
import { expect } from "chai";
|
||||
|
||||
import { CloudEvent, ValidationError, Version } from "../src";
|
||||
import { StructuredHTTPReceiver } from "../src/transport/http/structured_receiver";
|
||||
|
||||
const receiver = new StructuredHTTPReceiver(Version.V03);
|
||||
const type = "com.github.pull.create";
|
||||
const source = "urn:event:from:myapi/resourse/123";
|
||||
const time = new Date();
|
||||
const schemaurl = "http://cloudevents.io/schema.json";
|
||||
|
||||
const ceContentType = "application/json";
|
||||
|
||||
const data = {
|
||||
foo: "bar",
|
||||
};
|
||||
|
||||
describe("HTTP Transport Binding Structured Receiver CloudEvents v0.3", () => {
|
||||
describe("Check", () => {
|
||||
it("Throw error when payload arg is null or undefined", () => {
|
||||
// setup
|
||||
const payload = null;
|
||||
const attributes = {};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.parse.bind(receiver, (payload as unknown) as string, attributes)).to.throw(
|
||||
ValidationError,
|
||||
"payload is null or undefined",
|
||||
);
|
||||
});
|
||||
|
||||
it("Throw error when attributes arg is null or undefined", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = null;
|
||||
|
||||
expect(receiver.parse.bind(receiver, payload, (attributes as unknown) as string)).to.throw(
|
||||
ValidationError,
|
||||
"headers is null or undefined",
|
||||
);
|
||||
});
|
||||
|
||||
it("Throw error when payload is not an object or string", () => {
|
||||
// setup
|
||||
const payload = 1.0;
|
||||
const attributes = {};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.parse.bind(receiver, payload, attributes)).to.throw(
|
||||
ValidationError,
|
||||
"payload must be an object or a string",
|
||||
);
|
||||
});
|
||||
|
||||
it("Throw error when the content-type is invalid", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = {
|
||||
"Content-Type": "text/html",
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.parse.bind(receiver, payload, attributes)).to.throw(ValidationError, "invalid content type");
|
||||
});
|
||||
|
||||
it("Throw error data content encoding is base64, but 'data' is not", () => {
|
||||
// setup
|
||||
const event = {
|
||||
specversion: Version.V03,
|
||||
type,
|
||||
source,
|
||||
time,
|
||||
datacontenttype: "text/plain",
|
||||
datacontentencoding: "base64",
|
||||
schemaurl,
|
||||
data: "No base 64 value",
|
||||
};
|
||||
|
||||
const attributes = {
|
||||
"Content-Type": "application/cloudevents+json",
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.parse.bind(receiver, event, attributes)).to.throw(ValidationError, "invalid payload");
|
||||
});
|
||||
|
||||
it("No error when all required stuff are in place", () => {
|
||||
// setup
|
||||
const payload = {
|
||||
specversion: Version.V03,
|
||||
source,
|
||||
type,
|
||||
};
|
||||
const attributes = {
|
||||
"Content-Type": "application/cloudevents+json",
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.parse.bind(receiver, payload, attributes)).to.not.throw();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Parse", () => {
|
||||
it("Throw error when the event does not follow the spec", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = {
|
||||
"Content-Type": "application/cloudevents+json",
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.parse.bind(receiver, payload, attributes)).to.throw();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Parse", () => {
|
||||
it("Throw error when the event does not follow the spec", () => {
|
||||
const payload = {
|
||||
type,
|
||||
source,
|
||||
time,
|
||||
schemaurl,
|
||||
data,
|
||||
};
|
||||
|
||||
const headers = {
|
||||
"Content-Type": "application/cloudevents+xml",
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.parse.bind(receiver, payload, headers)).to.throw(ValidationError, "invalid content type");
|
||||
});
|
||||
|
||||
it("Should accept event that follows the spec", () => {
|
||||
// setup
|
||||
const id = "id-x0dk";
|
||||
const payload = {
|
||||
specversion: Version.V03,
|
||||
id,
|
||||
type,
|
||||
source,
|
||||
time,
|
||||
schemaurl,
|
||||
datacontenttype: ceContentType,
|
||||
data,
|
||||
};
|
||||
const headers = {
|
||||
"content-type": "application/cloudevents+json",
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, headers);
|
||||
|
||||
// assert
|
||||
expect(actual).to.be.an.instanceof(CloudEvent);
|
||||
expect(actual.id).to.equal(id);
|
||||
});
|
||||
|
||||
it("Should accept 'extension1'", () => {
|
||||
// setup
|
||||
const extension1 = "mycuston-ext1";
|
||||
const payload = {
|
||||
specversion: Version.V03,
|
||||
type,
|
||||
source,
|
||||
time,
|
||||
schemaurl,
|
||||
data,
|
||||
datacontenttype: ceContentType,
|
||||
extension1: extension1,
|
||||
};
|
||||
const headers = {
|
||||
"content-type": "application/cloudevents+json",
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, headers);
|
||||
|
||||
// assert
|
||||
expect(actual.extension1).to.equal(extension1);
|
||||
});
|
||||
|
||||
it("Should parse 'data' stringfied json to json object", () => {
|
||||
const payload = {
|
||||
specversion: Version.V03,
|
||||
type,
|
||||
source,
|
||||
time,
|
||||
schemaurl,
|
||||
datacontenttype: ceContentType,
|
||||
data: JSON.stringify(data),
|
||||
};
|
||||
const headers = {
|
||||
"content-type": "application/cloudevents+json",
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, headers);
|
||||
|
||||
// assert
|
||||
expect(actual.data).to.deep.equal(data);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,188 @@
|
|||
import "mocha";
|
||||
import { expect } from "chai";
|
||||
|
||||
import { CloudEvent, ValidationError, Version } from "../src";
|
||||
import { asBase64 } from "../src/event/validation";
|
||||
import { StructuredHTTPReceiver } from "../src/transport/http/structured_receiver";
|
||||
|
||||
const receiver = new StructuredHTTPReceiver(Version.V1);
|
||||
const type = "com.github.pull.create";
|
||||
const source = "urn:event:from:myapi/resourse/123";
|
||||
const time = new Date();
|
||||
const dataschema = "http://cloudevents.io/schema.json";
|
||||
|
||||
const ceContentType = "application/json";
|
||||
|
||||
const data = {
|
||||
foo: "bar",
|
||||
};
|
||||
|
||||
describe("HTTP Transport Binding Structured Receiver for CloudEvents v1.0", () => {
|
||||
describe("Check", () => {
|
||||
it("Throw error when payload arg is null or undefined", () => {
|
||||
// setup
|
||||
const payload = null;
|
||||
const attributes = {};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.parse.bind(receiver, (payload as unknown) as string, attributes)).to.throw(
|
||||
ValidationError,
|
||||
"payload is null or undefined",
|
||||
);
|
||||
});
|
||||
|
||||
it("Throw error when attributes arg is null or undefined", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = null;
|
||||
|
||||
expect(receiver.parse.bind(receiver, payload, (attributes as unknown) as string)).to.throw(
|
||||
ValidationError,
|
||||
"headers is null or undefined",
|
||||
);
|
||||
});
|
||||
|
||||
it("Throw error when payload is not an object or string", () => {
|
||||
// setup
|
||||
const payload = 1.0;
|
||||
const attributes = {};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.parse.bind(receiver, payload, attributes)).to.throw(
|
||||
ValidationError,
|
||||
"payload must be an object or a string",
|
||||
);
|
||||
});
|
||||
|
||||
it("Throw error when the content-type is invalid", () => {
|
||||
// setup
|
||||
const payload = {};
|
||||
const attributes = {
|
||||
"Content-Type": "text/html",
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.parse.bind(receiver, payload, attributes)).to.throw(ValidationError, "invalid content type");
|
||||
});
|
||||
|
||||
it("No error when all required stuff are in place", () => {
|
||||
// setup
|
||||
const payload = {
|
||||
source,
|
||||
type,
|
||||
};
|
||||
const attributes = {
|
||||
"Content-Type": "application/cloudevents+json",
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.parse.bind(receiver, payload, attributes)).to.not.throw();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Parse", () => {
|
||||
it("Throw error when the event does not follow the spec", () => {
|
||||
// setup
|
||||
const payload = {
|
||||
type,
|
||||
source,
|
||||
time,
|
||||
data,
|
||||
};
|
||||
|
||||
const headers = {
|
||||
"Content-Type": "application/cloudevents+xml",
|
||||
};
|
||||
|
||||
// act and assert
|
||||
expect(receiver.parse.bind(receiver, payload, headers)).to.throw(ValidationError, "invalid content type");
|
||||
});
|
||||
|
||||
it("Should accept event that follows the spec", () => {
|
||||
// setup
|
||||
const id = "id-x0dk";
|
||||
const payload = {
|
||||
id,
|
||||
type,
|
||||
source,
|
||||
time,
|
||||
data,
|
||||
dataschema,
|
||||
dataContentType: ceContentType,
|
||||
};
|
||||
const headers = {
|
||||
"content-type": "application/cloudevents+json",
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, headers);
|
||||
|
||||
// assert
|
||||
expect(actual).to.be.an.instanceof(CloudEvent);
|
||||
expect(actual.id).to.equal(id);
|
||||
});
|
||||
|
||||
it("Should accept 'extension1'", () => {
|
||||
// setup
|
||||
const extension1 = "mycustom-ext1";
|
||||
const event = {
|
||||
type,
|
||||
source,
|
||||
time,
|
||||
data,
|
||||
dataschema,
|
||||
dataContentType: ceContentType,
|
||||
extension1,
|
||||
};
|
||||
|
||||
const headers = {
|
||||
"content-type": "application/cloudevents+json",
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(event, headers);
|
||||
expect(actual.extension1).to.equal(extension1);
|
||||
});
|
||||
|
||||
it("Should parse 'data' stringified json to json object", () => {
|
||||
// setup
|
||||
const payload = {
|
||||
type,
|
||||
source,
|
||||
time,
|
||||
dataschema,
|
||||
data: data,
|
||||
dataContentType: ceContentType,
|
||||
};
|
||||
|
||||
const headers = {
|
||||
"content-type": "application/cloudevents+json",
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, headers);
|
||||
|
||||
// assert
|
||||
expect(actual.data).to.deep.equal(data);
|
||||
});
|
||||
|
||||
it("Should maps 'data_base64' to 'data' attribute", () => {
|
||||
const bindata = Uint32Array.from(JSON.stringify(data), (c) => c.codePointAt(0) as number);
|
||||
const expected = asBase64(bindata);
|
||||
const payload = {
|
||||
type,
|
||||
source,
|
||||
data: bindata,
|
||||
dataContentType: ceContentType,
|
||||
};
|
||||
|
||||
const headers = {
|
||||
"content-type": "application/cloudevents+json",
|
||||
};
|
||||
|
||||
// act
|
||||
const actual = receiver.parse(payload, headers);
|
||||
expect(actual.data_base64).to.equal(expected);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,46 +0,0 @@
|
|||
const expect = require("chai").expect;
|
||||
const { CloudEvent, HTTPReceiver, HTTPEmitter } = require("../");
|
||||
|
||||
const {
|
||||
SPEC_V03,
|
||||
SPEC_V1
|
||||
} = require("../lib/bindings/http/constants.js");
|
||||
|
||||
const fixture = {
|
||||
type: "org.cloudevents.test",
|
||||
source: "http://cloudevents.io"
|
||||
};
|
||||
|
||||
describe("The SDK Requirements", () => {
|
||||
it("should expose a CloudEvent type", () => {
|
||||
const event = new CloudEvent(fixture);
|
||||
expect(event instanceof CloudEvent).to.equal(true);
|
||||
});
|
||||
|
||||
it("should expose an HTTPReceiver type", () => {
|
||||
const receiver = new HTTPReceiver();
|
||||
expect(receiver instanceof HTTPReceiver).to.equal(true);
|
||||
});
|
||||
|
||||
it("should expose an HTTPEmitter type", () => {
|
||||
const emitter = new HTTPEmitter({
|
||||
url: "http://example.com"
|
||||
});
|
||||
expect(emitter instanceof HTTPEmitter).to.equal(true);
|
||||
});
|
||||
|
||||
describe("v0.3", () => {
|
||||
it("should create an event using the right spec version", () => {
|
||||
expect(new CloudEvent({
|
||||
specversion: SPEC_V03,
|
||||
...fixture
|
||||
}).spec.payload.specversion).to.equal(SPEC_V03);
|
||||
});
|
||||
});
|
||||
|
||||
describe("v1.0", () => {
|
||||
it("should create an event using the right spec version", () => {
|
||||
expect(new CloudEvent(fixture).spec.payload.specversion).to.equal(SPEC_V1);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,44 @@
|
|||
import "mocha";
|
||||
import { expect } from "chai";
|
||||
import { CloudEvent, Receiver, Emitter, Version } from "../src";
|
||||
|
||||
const fixture = {
|
||||
type: "org.cloudevents.test",
|
||||
source: "http://cloudevents.io",
|
||||
};
|
||||
|
||||
describe("The SDK Requirements", () => {
|
||||
it("should expose a CloudEvent type", () => {
|
||||
const event = new CloudEvent(fixture);
|
||||
expect(event instanceof CloudEvent).to.equal(true);
|
||||
});
|
||||
|
||||
it("should expose a Receiver type", () => {
|
||||
const receiver = new Receiver();
|
||||
expect(receiver instanceof Receiver).to.equal(true);
|
||||
});
|
||||
|
||||
it("should expose an Emitter type", () => {
|
||||
const emitter = new Emitter({
|
||||
url: "http://example.com",
|
||||
});
|
||||
expect(emitter instanceof Emitter).to.equal(true);
|
||||
});
|
||||
|
||||
describe("v0.3", () => {
|
||||
it("should create an event using the right spec version", () => {
|
||||
expect(
|
||||
new CloudEvent({
|
||||
...fixture,
|
||||
specversion: Version.V03,
|
||||
}).specversion,
|
||||
).to.equal(Version.V03);
|
||||
});
|
||||
});
|
||||
|
||||
describe("v1.0", () => {
|
||||
it("should create an event using the right spec version", () => {
|
||||
expect(new CloudEvent(fixture).specversion).to.equal(Version.V1);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,188 @@
|
|||
import "mocha";
|
||||
import { expect } from "chai";
|
||||
import { CloudEvent, Version, ValidationError, Mode } from "../src";
|
||||
import Constants from "../src/constants";
|
||||
|
||||
const id = "97699ec2-a8d9-47c1-bfa0-ff7aa526f838";
|
||||
const type = "com.github.pull.create";
|
||||
const source = "urn:event:from:myapi/resourse/123";
|
||||
const time = new Date();
|
||||
const schemaurl = "http://example.com/registry/myschema.json";
|
||||
const data = {
|
||||
much: "wow",
|
||||
};
|
||||
const subject = "subject-x0";
|
||||
|
||||
const cloudevent = new CloudEvent({
|
||||
specversion: Version.V03,
|
||||
id,
|
||||
source,
|
||||
type,
|
||||
subject,
|
||||
time,
|
||||
data,
|
||||
schemaurl,
|
||||
datacontenttype: Constants.MIME_JSON,
|
||||
});
|
||||
|
||||
describe("CloudEvents Spec v0.3", () => {
|
||||
describe("REQUIRED Attributes", () => {
|
||||
it("Should have 'id'", () => {
|
||||
expect(cloudevent.id).to.equal(id);
|
||||
});
|
||||
|
||||
it("Should have 'source'", () => {
|
||||
expect(cloudevent.source).to.equal(source);
|
||||
});
|
||||
|
||||
it("Should have 'specversion'", () => {
|
||||
expect(cloudevent.specversion).to.equal(Version.V03);
|
||||
});
|
||||
|
||||
it("Should have 'type'", () => {
|
||||
expect(cloudevent.type).to.equal(type);
|
||||
});
|
||||
});
|
||||
|
||||
describe("OPTIONAL Attributes", () => {
|
||||
it("Should have 'datacontentencoding'", () => {
|
||||
cloudevent.datacontentencoding = Constants.ENCODING_BASE64;
|
||||
expect(cloudevent.datacontentencoding).to.equal(Constants.ENCODING_BASE64);
|
||||
});
|
||||
|
||||
it("Should have 'datacontenttype'", () => {
|
||||
expect(cloudevent.datacontenttype).to.equal(Constants.MIME_JSON);
|
||||
});
|
||||
|
||||
it("Should have 'schemaurl'", () => {
|
||||
expect(cloudevent.schemaurl).to.equal(schemaurl);
|
||||
});
|
||||
|
||||
it("Should have 'subject'", () => {
|
||||
expect(cloudevent.subject).to.equal(subject);
|
||||
});
|
||||
|
||||
it("Should have 'time'", () => {
|
||||
expect(cloudevent.time).to.equal(time.toISOString());
|
||||
});
|
||||
|
||||
it("Should have 'data'", () => {
|
||||
expect(cloudevent.data).to.deep.equal(data);
|
||||
});
|
||||
|
||||
it("Should have the 'extension1'", () => {
|
||||
cloudevent.extension1 = "value1";
|
||||
expect(cloudevent.extension1).to.equal("value1");
|
||||
});
|
||||
});
|
||||
|
||||
describe("The Constraints check", () => {
|
||||
describe("'id'", () => {
|
||||
it("should throw an error when is absent", () => {
|
||||
delete cloudevent.id;
|
||||
expect(cloudevent.validate.bind(cloudevent)).to.throw(ValidationError, "invalid payload");
|
||||
cloudevent.id = id;
|
||||
});
|
||||
|
||||
it("should throw an error when is empty", () => {
|
||||
cloudevent.id = "";
|
||||
expect(cloudevent.validate.bind(cloudevent)).to.throw(ValidationError, "invalid payload");
|
||||
cloudevent.id = id;
|
||||
});
|
||||
});
|
||||
|
||||
describe("'source'", () => {
|
||||
it("should throw an error when is absent", () => {
|
||||
delete cloudevent.source;
|
||||
expect(cloudevent.validate.bind(cloudevent)).to.throw(ValidationError, "invalid payload");
|
||||
cloudevent.source = source;
|
||||
});
|
||||
});
|
||||
|
||||
describe("'specversion'", () => {
|
||||
it("should throw an error when is absent", () => {
|
||||
delete cloudevent.specversion;
|
||||
expect(cloudevent.validate.bind(cloudevent)).to.throw(ValidationError, "invalid payload");
|
||||
cloudevent.specversion = Version.V03;
|
||||
});
|
||||
});
|
||||
|
||||
describe("'type'", () => {
|
||||
it("should throw an error when is absent", () => {
|
||||
delete cloudevent.type;
|
||||
expect(cloudevent.validate.bind(cloudevent)).to.throw(ValidationError, "invalid payload");
|
||||
cloudevent.type = type;
|
||||
});
|
||||
|
||||
it("should throw an error when is an empty string", () => {
|
||||
cloudevent.type = "";
|
||||
expect(cloudevent.validate.bind(cloudevent)).to.throw(ValidationError, "invalid payload");
|
||||
cloudevent.type = type;
|
||||
});
|
||||
|
||||
it("must be a non-empty string", () => {
|
||||
cloudevent.type = type;
|
||||
expect(cloudevent.type).to.equal(type);
|
||||
});
|
||||
});
|
||||
|
||||
describe("'datacontentencoding'", () => {
|
||||
it("should throw an error when is a unsupported encoding", () => {
|
||||
cloudevent.data = "Y2xvdWRldmVudHMK";
|
||||
cloudevent.datacontentencoding = Mode.BINARY;
|
||||
expect(cloudevent.validate.bind(cloudevent)).to.throw(ValidationError, "invalid payload");
|
||||
delete cloudevent.datacontentencoding;
|
||||
cloudevent.data = data;
|
||||
});
|
||||
|
||||
it("should throw an error when 'data' does not carry base64", () => {
|
||||
cloudevent.data = "no base 64 value";
|
||||
cloudevent.datacontentencoding = Constants.ENCODING_BASE64;
|
||||
cloudevent.datacontenttype = "text/plain";
|
||||
|
||||
expect(cloudevent.validate.bind(cloudevent)).to.throw(ValidationError, "invalid payload");
|
||||
|
||||
delete cloudevent.datacontentencoding;
|
||||
cloudevent.data = data;
|
||||
});
|
||||
|
||||
it("should accept when 'data' is a string", () => {
|
||||
cloudevent.data = "Y2xvdWRldmVudHMK";
|
||||
cloudevent.datacontentencoding = Constants.ENCODING_BASE64;
|
||||
expect(cloudevent.validate()).to.be.true;
|
||||
delete cloudevent.datacontentencoding;
|
||||
cloudevent.data = data;
|
||||
});
|
||||
});
|
||||
|
||||
describe("'data'", () => {
|
||||
it("should maintain the type of data when no data content type", () => {
|
||||
delete cloudevent.datacontenttype;
|
||||
cloudevent.data = JSON.stringify(data);
|
||||
|
||||
expect(typeof cloudevent.data).to.equal("string");
|
||||
cloudevent.datacontenttype = Constants.MIME_JSON;
|
||||
});
|
||||
|
||||
it("should convert data with stringified json to a json object", () => {
|
||||
cloudevent.datacontenttype = Constants.MIME_JSON;
|
||||
cloudevent.data = JSON.stringify(data);
|
||||
expect(cloudevent.data).to.deep.equal(data);
|
||||
});
|
||||
});
|
||||
|
||||
describe("'subject'", () => {
|
||||
it("should throw an error when is an empty string", () => {
|
||||
cloudevent.subject = "";
|
||||
expect(cloudevent.validate.bind(cloudevent)).to.throw(ValidationError, "invalid payload");
|
||||
cloudevent.subject = subject;
|
||||
});
|
||||
});
|
||||
|
||||
describe("'time'", () => {
|
||||
it("must adhere to the format specified in RFC 3339", () => {
|
||||
expect(cloudevent.time).to.equal(time.toISOString());
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,218 +0,0 @@
|
|||
const expect = require("chai").expect;
|
||||
const { CloudEvent } = require("../index.js");
|
||||
const {
|
||||
MIME_JSON,
|
||||
ENCODING_BASE64,
|
||||
SPEC_V03,
|
||||
BINARY
|
||||
} = require("../lib/bindings/http/constants.js");
|
||||
const ValidationError = require("../lib/bindings/http/validation/validation_error.js");
|
||||
|
||||
const id = "97699ec2-a8d9-47c1-bfa0-ff7aa526f838";
|
||||
const type = "com.github.pull.create";
|
||||
const source = "urn:event:from:myapi/resourse/123";
|
||||
const time = new Date();
|
||||
const schemaURL = "http://example.com/registry/myschema.json";
|
||||
const data = {
|
||||
much: "wow"
|
||||
};
|
||||
const subject = "subject-x0";
|
||||
|
||||
const cloudevent = new CloudEvent({
|
||||
specversion: SPEC_V03,
|
||||
id,
|
||||
source,
|
||||
type,
|
||||
subject,
|
||||
time,
|
||||
data,
|
||||
schemaURL,
|
||||
dataContentType: MIME_JSON
|
||||
});
|
||||
|
||||
describe("CloudEvents Spec v0.3", () => {
|
||||
describe("REQUIRED Attributes", () => {
|
||||
it("Should have 'id'", () => {
|
||||
expect(cloudevent.id).to.equal(id);
|
||||
});
|
||||
|
||||
it("Should have 'source'", () => {
|
||||
expect(cloudevent.source).to.equal(source);
|
||||
});
|
||||
|
||||
it("Should have 'specversion'", () => {
|
||||
expect(cloudevent.specversion).to.equal(SPEC_V03);
|
||||
});
|
||||
|
||||
it("Should have 'type'", () => {
|
||||
expect(cloudevent.type).to.equal(type);
|
||||
});
|
||||
});
|
||||
|
||||
describe("OPTIONAL Attributes", () => {
|
||||
it("Should have 'datacontentencoding'", () => {
|
||||
cloudevent.dataContentEncoding = ENCODING_BASE64;
|
||||
expect(cloudevent.spec.payload.datacontentencoding)
|
||||
.to.equal(ENCODING_BASE64);
|
||||
delete cloudevent.spec.payload.datacontentencoding;
|
||||
});
|
||||
|
||||
it("Should have 'datacontenttype'", () => {
|
||||
expect(cloudevent.dataContentType).to.equal(MIME_JSON);
|
||||
});
|
||||
|
||||
it("Should have 'schemaurl'", () => {
|
||||
expect(cloudevent.schemaURL).to.equal(schemaURL);
|
||||
});
|
||||
|
||||
it("Should have 'subject'", () => {
|
||||
expect(cloudevent.subject).to.equal(subject);
|
||||
});
|
||||
|
||||
it("Should have 'time'", () => {
|
||||
expect(cloudevent.time).to.equal(time.toISOString());
|
||||
});
|
||||
|
||||
it("Should have 'data'", () => {
|
||||
expect(cloudevent.data).to.deep.equal(data);
|
||||
});
|
||||
|
||||
it("Should have the 'extension1'", () => {
|
||||
cloudevent.addExtension("extension1", "value1");
|
||||
expect(cloudevent.spec.payload.extension1)
|
||||
.to.equal("value1");
|
||||
});
|
||||
|
||||
it("should throw an error when use a reserved name as extension", () => {
|
||||
expect(cloudevent.addExtension.bind(cloudevent, "id"))
|
||||
.to.throw(ValidationError, "Reserved attribute name: 'id'");
|
||||
});
|
||||
});
|
||||
|
||||
describe("The Constraints check", () => {
|
||||
describe("'id'", () => {
|
||||
it("should throw an error when is absent", () => {
|
||||
delete cloudevent.spec.payload.id;
|
||||
expect(cloudevent.format.bind(cloudevent))
|
||||
.to.throw(ValidationError, "invalid payload");
|
||||
cloudevent.spec.payload.id = id;
|
||||
});
|
||||
|
||||
it("should throw an error when is empty", () => {
|
||||
cloudevent.spec.payload.id = "";
|
||||
expect(cloudevent.format.bind(cloudevent))
|
||||
.to.throw(ValidationError, "invalid payload");
|
||||
cloudevent.spec.payload.id = id;
|
||||
});
|
||||
});
|
||||
|
||||
describe("'source'", () => {
|
||||
it("should throw an error when is absent", () => {
|
||||
delete cloudevent.spec.payload.source;
|
||||
expect(cloudevent.format.bind(cloudevent))
|
||||
.to.throw(ValidationError, "invalid payload");
|
||||
cloudevent.spec.payload.source = source;
|
||||
});
|
||||
});
|
||||
|
||||
describe("'specversion'", () => {
|
||||
it("should throw an error when is absent", () => {
|
||||
delete cloudevent.spec.payload.specversion;
|
||||
expect(cloudevent.format.bind(cloudevent))
|
||||
.to.throw(ValidationError, "invalid payload");
|
||||
cloudevent.spec.payload.specversion = SPEC_V03;
|
||||
});
|
||||
|
||||
it("should throw an error when is empty", () => {
|
||||
cloudevent.spec.payload.specversion = "";
|
||||
expect(cloudevent.format.bind(cloudevent))
|
||||
.to.throw(ValidationError, "invalid payload");
|
||||
cloudevent.spec.payload.specversion = SPEC_V03;
|
||||
});
|
||||
});
|
||||
|
||||
describe("'type'", () => {
|
||||
it("should throw an error when is absent", () => {
|
||||
delete cloudevent.spec.payload.type;
|
||||
expect(cloudevent.format.bind(cloudevent))
|
||||
.to.throw(ValidationError, "invalid payload");
|
||||
cloudevent.spec.payload.type = type;
|
||||
});
|
||||
|
||||
it("should throw an error when is an empty string", () => {
|
||||
cloudevent.type = "";
|
||||
expect(cloudevent.format.bind(cloudevent))
|
||||
.to.throw(ValidationError, "invalid payload");
|
||||
cloudevent.type = type;
|
||||
});
|
||||
|
||||
it("must be a non-empty string", () => {
|
||||
cloudevent.type = type;
|
||||
expect(cloudevent.spec.payload.type).to.equal(type);
|
||||
});
|
||||
});
|
||||
|
||||
describe("'datacontentencoding'", () => {
|
||||
it("should throw an error when is a unsupported encoding", () => {
|
||||
cloudevent.data = "Y2xvdWRldmVudHMK";
|
||||
cloudevent.dataContentEncoding = BINARY;
|
||||
expect(cloudevent.format.bind(cloudevent))
|
||||
.to.throw(ValidationError, "invalid payload");
|
||||
delete cloudevent.spec.payload.datacontentencoding;
|
||||
cloudevent.data = data;
|
||||
});
|
||||
|
||||
it("should throw an error when 'data' does not carry base64",
|
||||
() => {
|
||||
cloudevent.data = "no base 64 value";
|
||||
cloudevent.dataContentEncoding = ENCODING_BASE64;
|
||||
cloudevent.dataContentType = "text/plain";
|
||||
|
||||
expect(cloudevent.format.bind(cloudevent))
|
||||
.to.throw(ValidationError, "invalid payload");
|
||||
|
||||
delete cloudevent.spec.payload.datacontentencoding;
|
||||
cloudevent.data = data;
|
||||
});
|
||||
|
||||
it("should accept when 'data' is a string", () => {
|
||||
cloudevent.data = "Y2xvdWRldmVudHMK";
|
||||
cloudevent.dataContentEncoding = ENCODING_BASE64;
|
||||
expect(cloudevent.format()).to.have.property("datacontentencoding");
|
||||
delete cloudevent.spec.payload.datacontentencoding;
|
||||
cloudevent.data = data;
|
||||
});
|
||||
});
|
||||
|
||||
describe("'data'", () => {
|
||||
it("should maintain the type of data when no data content type", () => {
|
||||
delete cloudevent.spec.payload.datacontenttype;
|
||||
cloudevent.data = JSON.stringify(data);
|
||||
|
||||
expect(typeof cloudevent.data).to.equal("string");
|
||||
cloudevent.dataContentType = MIME_JSON;
|
||||
});
|
||||
|
||||
it("should convert data with stringified json to a json object", () => {
|
||||
cloudevent.dataContentType = MIME_JSON;
|
||||
cloudevent.data = JSON.stringify(data);
|
||||
expect(cloudevent.data).to.deep.equal(data);
|
||||
});
|
||||
});
|
||||
|
||||
describe("'subject'", () => {
|
||||
it("should throw an error when is an empty string", () => {
|
||||
cloudevent.subject = "";
|
||||
expect(cloudevent.format.bind(cloudevent))
|
||||
.to.throw(ValidationError, "invalid payload");
|
||||
cloudevent.subject = subject;
|
||||
});
|
||||
});
|
||||
|
||||
describe("'time'", () => {
|
||||
it("must adhere to the format specified in RFC 3339", () => {
|
||||
expect(cloudevent.format().time).to.equal(time.toISOString());
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,237 +0,0 @@
|
|||
const expect = require("chai").expect;
|
||||
const { CloudEvent } = require("../index.js");
|
||||
const { v4: uuidv4 } = require("uuid");
|
||||
const { asBase64 } = require("../lib/bindings/http/validation/fun.js");
|
||||
const ValidationError = require("../lib/bindings/http/validation/validation_error.js");
|
||||
const {
|
||||
SPEC_V1
|
||||
} = require("../lib/bindings/http/constants.js");
|
||||
|
||||
const id = uuidv4();
|
||||
const type = "com.github.pull.create";
|
||||
const source = "urn:event:from:myapi/resource/123";
|
||||
const time = new Date();
|
||||
const dataSchema = "http://example.com/registry/myschema.json";
|
||||
const dataContentType = "application/json";
|
||||
const data = {
|
||||
much: "wow"
|
||||
};
|
||||
const subject = "subject-x0";
|
||||
|
||||
const cloudevent = new CloudEvent({
|
||||
specversion: SPEC_V1,
|
||||
id,
|
||||
source,
|
||||
type,
|
||||
dataContentType,
|
||||
dataSchema,
|
||||
subject,
|
||||
time,
|
||||
data
|
||||
});
|
||||
|
||||
describe("CloudEvents Spec v1.0", () => {
|
||||
describe("REQUIRED Attributes", () => {
|
||||
it("Should have 'id'", () => {
|
||||
expect(cloudevent.id).to.equal(id);
|
||||
});
|
||||
|
||||
it("Should have 'source'", () => {
|
||||
expect(cloudevent.source).to.equal(source);
|
||||
});
|
||||
|
||||
it("Should have 'specversion'", () => {
|
||||
expect(cloudevent.specversion).to.equal("1.0");
|
||||
});
|
||||
|
||||
it("Should have 'type'", () => {
|
||||
expect(cloudevent.type).to.equal(type);
|
||||
});
|
||||
});
|
||||
|
||||
describe("OPTIONAL Attributes", () => {
|
||||
it("Should have 'datacontenttype'", () => {
|
||||
expect(cloudevent.dataContentType).to.equal(dataContentType);
|
||||
});
|
||||
|
||||
it("Should have 'dataschema'", () => {
|
||||
expect(cloudevent.dataSchema).to.equal(dataSchema);
|
||||
});
|
||||
|
||||
it("Should have 'subject'", () => {
|
||||
expect(cloudevent.subject).to.equal(subject);
|
||||
});
|
||||
|
||||
it("Should have 'time'", () => {
|
||||
expect(cloudevent.time).to.equal(time.toISOString());
|
||||
});
|
||||
});
|
||||
|
||||
describe("Extensions Constraints", () => {
|
||||
it("should be ok when type is 'boolean'", () => {
|
||||
cloudevent.addExtension("ext-boolean", true);
|
||||
expect(cloudevent.spec.payload["ext-boolean"])
|
||||
.to.equal(true);
|
||||
});
|
||||
|
||||
it("should be ok when type is 'integer'", () => {
|
||||
cloudevent.addExtension("ext-integer", 2019);
|
||||
expect(cloudevent.spec.payload["ext-integer"])
|
||||
.to.equal(2019);
|
||||
});
|
||||
|
||||
it("should be ok when type is 'string'", () => {
|
||||
cloudevent.addExtension("ext-string", "an-string");
|
||||
expect(cloudevent.spec.payload["ext-string"])
|
||||
.to.equal("an-string");
|
||||
});
|
||||
|
||||
it("should be ok when type is 'Uint32Array' for 'Binary'", () => {
|
||||
const myBinary = new Uint32Array(2019);
|
||||
cloudevent.addExtension("ext-binary", myBinary);
|
||||
expect(cloudevent.spec.payload["ext-binary"])
|
||||
.to.equal(myBinary);
|
||||
});
|
||||
|
||||
// URI
|
||||
|
||||
it("should be ok when type is 'Date' for 'Timestamp'", () => {
|
||||
const myDate = new Date();
|
||||
cloudevent.addExtension("ext-date", myDate);
|
||||
expect(cloudevent.spec.payload["ext-date"])
|
||||
.to.equal(myDate);
|
||||
});
|
||||
|
||||
it("Should have the 'extension1'", () => {
|
||||
cloudevent.addExtension("extension1", "value1");
|
||||
expect(cloudevent.spec.payload.extension1)
|
||||
.to.equal("value1");
|
||||
});
|
||||
|
||||
it("should throw an error when use a reserved name as extension", () => {
|
||||
expect(cloudevent.addExtension.bind(cloudevent, "id"))
|
||||
.to.throw(ValidationError, "Reserved attribute name: 'id'");
|
||||
});
|
||||
|
||||
it("should throw an error when use an invalid type", () => {
|
||||
expect(cloudevent
|
||||
.addExtension.bind(cloudevent, "invalid-val", { cool: "nice" }))
|
||||
.to.throw(ValidationError, "Invalid type of extension value");
|
||||
});
|
||||
});
|
||||
|
||||
describe("The Constraints check", () => {
|
||||
describe("'id'", () => {
|
||||
it("should throw an error when is absent", () => {
|
||||
delete cloudevent.spec.payload.id;
|
||||
expect(cloudevent.format.bind(cloudevent))
|
||||
.to
|
||||
.throw(ValidationError, "invalid payload");
|
||||
cloudevent.spec.payload.id = id;
|
||||
});
|
||||
|
||||
it("should throw an error when is empty", () => {
|
||||
cloudevent.spec.payload.id = "";
|
||||
expect(cloudevent.format.bind(cloudevent))
|
||||
.to.throw(ValidationError, "invalid payload");
|
||||
cloudevent.spec.payload.id = id;
|
||||
});
|
||||
});
|
||||
|
||||
describe("'source'", () => {
|
||||
it("should throw an error when is absent", () => {
|
||||
delete cloudevent.spec.payload.source;
|
||||
expect(cloudevent.format.bind(cloudevent))
|
||||
.to.throw(ValidationError, "invalid payload");
|
||||
cloudevent.spec.payload.source = source;
|
||||
});
|
||||
});
|
||||
|
||||
describe("'specversion'", () => {
|
||||
it("should throw an error when is absent", () => {
|
||||
delete cloudevent.spec.payload.specversion;
|
||||
expect(cloudevent.format.bind(cloudevent))
|
||||
.to.throw(ValidationError, "invalid payload");
|
||||
cloudevent.spec.payload.specversion = "1.0";
|
||||
});
|
||||
|
||||
it("should throw an error when is empty", () => {
|
||||
cloudevent.spec.payload.specversion = "";
|
||||
expect(cloudevent.format.bind(cloudevent))
|
||||
.to.throw(ValidationError, "invalid payload");
|
||||
cloudevent.spec.payload.specversion = "1.0";
|
||||
});
|
||||
});
|
||||
|
||||
describe("'type'", () => {
|
||||
it("should throw an error when is absent", () => {
|
||||
delete cloudevent.spec.payload.type;
|
||||
expect(cloudevent.format.bind(cloudevent))
|
||||
.to.throw(ValidationError, "invalid payload");
|
||||
cloudevent.spec.payload.type = type;
|
||||
});
|
||||
|
||||
it("should throw an error when is an empty string", () => {
|
||||
cloudevent.type = "";
|
||||
expect(cloudevent.format.bind(cloudevent))
|
||||
.to.throw(ValidationError, "invalid payload");
|
||||
cloudevent.type = type;
|
||||
});
|
||||
|
||||
it("must be a non-empty string", () => {
|
||||
cloudevent.type = type;
|
||||
expect(cloudevent.spec.payload.type).to.equal(type);
|
||||
});
|
||||
});
|
||||
|
||||
describe("'subject'", () => {
|
||||
it("should throw an error when is an empty string", () => {
|
||||
cloudevent.subject = "";
|
||||
expect(cloudevent.format.bind(cloudevent))
|
||||
.to.throw(ValidationError, "invalid payload");
|
||||
cloudevent.subject = type;
|
||||
});
|
||||
});
|
||||
|
||||
describe("'time'", () => {
|
||||
it("must adhere to the format specified in RFC 3339", () => {
|
||||
cloudevent.time = time;
|
||||
expect(cloudevent.format().time).to.equal(time.toISOString());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Event data constraints", () => {
|
||||
it("Should have 'data'", () => {
|
||||
expect(cloudevent.data).to.deep.equal(data);
|
||||
});
|
||||
|
||||
it("should maintain the type of data when no data content type", () => {
|
||||
delete cloudevent.spec.payload.datacontenttype;
|
||||
cloudevent.data = JSON.stringify(data);
|
||||
|
||||
expect(typeof cloudevent.data).to.equal("string");
|
||||
cloudevent.dataContentType = dataContentType;
|
||||
});
|
||||
|
||||
it("should convert data with stringified json to a json object", () => {
|
||||
cloudevent.dataContentType = dataContentType;
|
||||
cloudevent.data = JSON.stringify(data);
|
||||
expect(cloudevent.data).to.deep.equal(data);
|
||||
});
|
||||
|
||||
it("should be ok when type is 'Uint32Array' for 'Binary'", () => {
|
||||
const dataString = ")(*~^my data for ce#@#$%";
|
||||
|
||||
const dataBinary = Uint32Array.from(dataString, (c) => c.codePointAt(0));
|
||||
const expected = asBase64(dataBinary);
|
||||
const olddct = cloudevent.dataContentType;
|
||||
|
||||
cloudevent.dataContentType = "text/plain";
|
||||
cloudevent.data = dataBinary;
|
||||
expect(cloudevent.data).to.deep.equal(expected);
|
||||
|
||||
cloudevent.dataContentType = olddct;
|
||||
});
|
||||
});
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue