Initial release
[websub-hub] / test-e2e / fake-servers.js
diff --git a/test-e2e/fake-servers.js b/test-e2e/fake-servers.js
new file mode 100644 (file)
index 0000000..0adff34
--- /dev/null
@@ -0,0 +1,174 @@
+/* eslint-disable sonarjs/no-duplicate-string */
+'use strict';
+
+/**
+ * For testing, this is a configurable endpoint server.
+ * 
+ * Set how a subscriber id responds to a verification GET call
+ *   PUT /subscriber/:id/verify?responseCode=xxx&matchChallenge=true
+ * Set how an id responds to a content delivery POST call
+ *   PUT /subscriber/:id/content?responseCode=xxx
+ * Remove an id
+ *   DELETE /subscriber/:id
+ * 
+ * Set how a topic id returns
+ *   PUT /topic/:id?statusCode=xxx&content=xxx&contentType=foo/bar
+ */
+
+const http = require('http');
+const { Dingus, Enum, Errors } = require('@squeep/api-dingus');
+
+const subscriberPort = process.env.FAKE_SUBSCRIBER_PORT || 9876;
+const topicPort = process.env.FAKE_TOPIC_PORT || 9875;
+const listenAddress = process.env.FAKE_LISTEN_ADDR || '127.0.0.1';
+
+class TopicFake extends Dingus {
+  constructor() {
+    super(console, {
+      ignoreTrailingSlash: true,
+    });
+    this.topicBehaviors = new Map();
+    this.on(['GET'], '/topic/:id', this.getId.bind(this));
+
+    this.on(['PUT'], '/topic/:id', this.putId.bind(this));
+    this.on(['DELETE'], '/topic/:id', this.deleteId.bind(this));
+  }
+
+  async getId(req, res, ctx) {
+    this.setResponseType(this.responseTypes, req, res, ctx);
+    const behavior = this.topicBehaviors.get(ctx.params.id);
+    if (!behavior) {
+      throw new Errors.ResponseError(Enum.ErrorResponse.NotFound);
+    }
+    if (behavior.contentType) {
+      res.setHeader(Enum.Header.ContentType, behavior.contentType);
+    }
+    res.setHeader('Link', behavior.selfLink + (behavior.hubLink ? `, ${behavior.hubLink}` : ''));
+    res.statusCode = behavior.statusCode;
+    res.end(behavior.content);
+    this.logger.info({ method: req.method, statusCode: res.statusCode });
+  }
+
+  async putId(req, res, ctx) {
+    const id = ctx.params.id;
+    this.setResponseType(this.responseTypes, req, res, ctx);
+    const behavior = {
+      statusCode: ctx.queryParams.statusCode || 200,
+      ...(ctx.queryParams.contentType && { contentType: ctx.queryParams.contentType }),
+      content: ctx.queryParams.content,
+      selfLink: `<http://${listenAddress}:${topicPort}/${id}>; rel="self"`,
+      ...(ctx.queryParams.hubUrl && { hubLink: `<${ctx.queryParams.hubUrl}>; rel="hub"` }),
+    };
+    this.topicBehaviors.set(id, behavior);
+    res.statusCode = 200;
+    res.end();
+  }
+
+  async deleteId(req, res, ctx) {
+    this.setResponseType(this.responseTypes, req, res, ctx);
+    this.topicBehaviors.delete(ctx.params.id);
+    res.statusCode = 200;
+    res.end();
+  }
+
+} // TopicFake
+
+class SubscriberFake extends Dingus {
+  constructor() {
+    super(console, {
+      ignoreTrailingSlash: true,
+    });
+    this.verifyBehaviors = new Map();
+    this.contentBehaviors = new Map();
+    this.on(['GET'], '/subscriber/:id', this.getId.bind(this));
+    this.on(['POST'], '/subscriber/:id', this.postId.bind(this));
+
+    this.on(['PUT'], '/subscriber/:id/verify', this.putVerify.bind(this));
+    this.on(['PUT'], '/subscriber/:id/content', this.putContent.bind(this));
+    this.on(['DELETE'], '/subscriber/:id', this.deleteId.bind(this));
+  }
+
+  // eslint-disable-next-line class-methods-use-this
+  parseBody() {
+    // do not parse, just ingest
+  }
+
+  async getId(req, res, ctx) {
+    this.setResponseType(this.responseTypes, req, res, ctx);
+    const behavior = this.verifyBehaviors.get(ctx.params.id);
+    res.statusCode = behavior ? behavior.statusCode : 404;
+    const response = (behavior && behavior.matchChallenge) ? ctx.queryParams['hub.challenge'] : (behavior && behavior.response);
+    res.end(response);
+    this.logger.info({ method: req.method, statusCode: res.statusCode, matchChallenge: !!(behavior && behavior.matchChallenge) });
+  }
+
+  async postId(req, res, ctx) {
+    this.setResponseType(this.responseTypes, req, res, ctx);
+    await this.ingestBody(req, res, ctx);
+    const behavior = this.contentBehaviors.get(ctx.params.id);
+    res.statusCode = behavior ? behavior.statusCode : 404;
+    if (behavior) {
+      behavior.updated = new Date();
+      behavior.content = ctx.rawBody;
+    }
+    res.end();
+    this.logger.info({ content: behavior && behavior.content, method: req.method, statusCode: res.statusCode, matchChallenge: !!(behavior && behavior.matchChallenge) });
+  }
+
+  async putVerify(req, res, ctx) {
+    this.setResponseType(this.responseTypes, req, res, ctx);
+    const behavior = {
+      matchChallenge: ctx.queryParams.matchChallenge === 'true',
+      statusCode: ctx.queryParams.statusCode || 200,
+    };
+    this.verifyBehaviors.set(ctx.params.id, behavior);
+    if (!this.contentBehaviors.get(ctx.params.id)) {
+      this.contentBehaviors.set(ctx.params.id, {
+        statusCode: 200,
+      });
+    }
+    res.statusCode = 200;
+    res.end();
+  }
+
+  async putContent(req, res, ctx) {
+    this.setResponseType(this.responseTypes, req, res, ctx);
+    const behavior = {
+      statusCode: ctx.queryParams.statusCode || 200,
+    };
+    this.contentBehaviors.set(ctx.params.id, behavior);
+    res.statusCode = 200;
+    res.end();
+  }
+
+  async deleteId(req, res, ctx) {
+      this.setResponseType(this.responseTypes, req, res, ctx);
+      this.contentBehaviors.delete(ctx.params.id);
+      this.verifyBehaviors.delete(ctx.params.id);
+      res.statusCode = 200;
+      res.end();
+  }
+
+} // SubscriberFake
+
+const subscriberService = new SubscriberFake();
+http.createServer((req, res) => {
+  subscriberService.dispatch(req, res);
+}).listen(subscriberPort, listenAddress, (err) => {
+  if (err) {
+    console.error(err);
+    throw err;
+  }
+  console.log(`Fake Subscriber Server started on ${listenAddress}:${subscriberPort}`);
+});
+
+const topicService = new TopicFake();
+http.createServer((req, res) => {
+  topicService.dispatch(req, res);
+}).listen(topicPort, listenAddress, (err) => {
+  if (err) {
+    console.error(err);
+    throw err;
+  }
+  console.log(`Fake Topic Server started on ${listenAddress}:${topicPort}`);
+});