update dependencies, devDependencies, copyright
[websub-hub] / test-e2e / fake-servers.js
1 /* eslint-disable sonarjs/no-duplicate-string */
2 'use strict';
3
4 /**
5 * For testing, this is a configurable endpoint server.
6 *
7 * Set how a subscriber id responds to a verification GET call
8 * PUT /subscriber/:id/verify?responseCode=xxx&matchChallenge=true
9 * Set how an id responds to a content delivery POST call
10 * PUT /subscriber/:id/content?responseCode=xxx
11 * Remove an id
12 * DELETE /subscriber/:id
13 *
14 * Set how a topic id returns
15 * PUT /topic/:id?statusCode=xxx&content=xxx&contentType=foo/bar
16 */
17
18 const http = require('http');
19 const { Dingus, Enum, Errors } = require('@squeep/api-dingus');
20
21 const subscriberPort = process.env.FAKE_SUBSCRIBER_PORT || 9876;
22 const topicPort = process.env.FAKE_TOPIC_PORT || 9875;
23 const listenAddress = process.env.FAKE_LISTEN_ADDR || '127.0.0.1';
24
25 class TopicFake extends Dingus {
26 constructor() {
27 super(console, {
28 ignoreTrailingSlash: true,
29 });
30 this.topicBehaviors = new Map();
31 this.on(['GET'], '/topic/:id', this.getId.bind(this));
32
33 this.on(['PUT'], '/topic/:id', this.putId.bind(this));
34 this.on(['DELETE'], '/topic/:id', this.deleteId.bind(this));
35 }
36
37 async getId(req, res, ctx) {
38 this.setResponseType(this.responseTypes, req, res, ctx);
39 const behavior = this.topicBehaviors.get(ctx.params.id);
40 if (!behavior) {
41 throw new Errors.ResponseError(Enum.ErrorResponse.NotFound);
42 }
43 if (behavior.contentType) {
44 res.setHeader(Enum.Header.ContentType, behavior.contentType);
45 }
46 res.setHeader('Link', behavior.selfLink + (behavior.hubLink ? `, ${behavior.hubLink}` : ''));
47 res.statusCode = behavior.statusCode;
48 res.end(behavior.content);
49 this.logger.info('TopicFake:getId', { method: req.method, statusCode: res.statusCode, url: req.url });
50 }
51
52 async putId(req, res, ctx) {
53 const id = ctx.params.id;
54 this.setResponseType(this.responseTypes, req, res, ctx);
55 const behavior = {
56 statusCode: ctx.queryParams.statusCode || 200,
57 ...(ctx.queryParams.contentType && { contentType: ctx.queryParams.contentType }),
58 content: ctx.queryParams.content,
59 selfLink: `<http://${listenAddress}:${topicPort}/${id}>; rel="self"`,
60 ...(ctx.queryParams.hubUrl && { hubLink: `<${ctx.queryParams.hubUrl}>; rel="hub"` }),
61 };
62 this.topicBehaviors.set(id, behavior);
63 res.statusCode = 200;
64 this.logger.info('TopicFake:putId', { method: req.method, statusCode: res.statusCode, url: req.url });
65 res.end();
66 }
67
68 async deleteId(req, res, ctx) {
69 this.setResponseType(this.responseTypes, req, res, ctx);
70 this.topicBehaviors.delete(ctx.params.id);
71 res.statusCode = 200;
72 this.logger.info('TopicFake:deleteId', { method: req.method, statusCode: res.statusCode, url: req.url });
73 res.end();
74 }
75
76 } // TopicFake
77
78 class SubscriberFake extends Dingus {
79 constructor() {
80 super(console, {
81 ignoreTrailingSlash: true,
82 });
83 this.verifyBehaviors = new Map();
84 this.contentBehaviors = new Map();
85 this.on(['GET'], '/subscriber/:id', this.getId.bind(this));
86 this.on(['POST'], '/subscriber/:id', this.postId.bind(this));
87
88 this.on(['PUT'], '/subscriber/:id/verify', this.putVerify.bind(this));
89 this.on(['PUT'], '/subscriber/:id/content', this.putContent.bind(this));
90 this.on(['DELETE'], '/subscriber/:id', this.deleteId.bind(this));
91 }
92
93 // eslint-disable-next-line class-methods-use-this
94 parseBody() {
95 // do not parse, just ingest
96 }
97
98 async getId(req, res, ctx) {
99 this.setResponseType(this.responseTypes, req, res, ctx);
100 const behavior = this.verifyBehaviors.get(ctx.params.id);
101 res.statusCode = behavior ? behavior.statusCode : 404;
102 const response = (behavior?.matchChallenge) ? ctx.queryParams['hub.challenge'] : (behavior?.response);
103 res.end(response);
104 this.logger.info('SubscriberFake:getId', { method: req.method, statusCode: res.statusCode, matchChallenge: !!(behavior?.matchChallenge), url: req.url });
105 }
106
107 async postId(req, res, ctx) {
108 this.setResponseType(this.responseTypes, req, res, ctx);
109 await this.ingestBody(req, res, ctx);
110 const behavior = this.contentBehaviors.get(ctx.params.id);
111 res.statusCode = behavior ? behavior.statusCode : 404;
112 if (behavior) {
113 behavior.updated = new Date();
114 behavior.content = ctx.rawBody;
115 }
116 res.end();
117 this.logger.info('SubscriberFake:postId', { content: behavior?.content, method: req.method, statusCode: res.statusCode, matchChallenge: !!(behavior?.matchChallenge), url: req.url });
118 }
119
120 async putVerify(req, res, ctx) {
121 this.setResponseType(this.responseTypes, req, res, ctx);
122 const behavior = {
123 matchChallenge: ctx.queryParams.matchChallenge === 'true',
124 statusCode: ctx.queryParams.statusCode || 200,
125 };
126 this.verifyBehaviors.set(ctx.params.id, behavior);
127 if (!this.contentBehaviors.get(ctx.params.id)) {
128 this.contentBehaviors.set(ctx.params.id, {
129 statusCode: 200,
130 });
131 }
132 res.statusCode = 200;
133 res.end();
134 this.logger.info('SubscriberFake:putVerify', { method: req.method, statusCode: res.statusCode, url: req.url });
135 }
136
137 async putContent(req, res, ctx) {
138 this.setResponseType(this.responseTypes, req, res, ctx);
139 const behavior = {
140 statusCode: ctx.queryParams.statusCode || 200,
141 };
142 this.contentBehaviors.set(ctx.params.id, behavior);
143 res.statusCode = 200;
144 res.end();
145 this.logger.info('SubscriberFake:putContent', { method: req.method, statusCode: res.statusCode, url: req.url });
146 }
147
148 async deleteId(req, res, ctx) {
149 this.setResponseType(this.responseTypes, req, res, ctx);
150 this.contentBehaviors.delete(ctx.params.id);
151 this.verifyBehaviors.delete(ctx.params.id);
152 res.statusCode = 200;
153 res.end();
154 this.logger.info('SubscriberFake:deleteId', { method: req.method, statusCode: res.statusCode, url: req.url });
155 }
156
157 } // SubscriberFake
158
159 const subscriberService = new SubscriberFake();
160 http.createServer((req, res) => {
161 subscriberService.dispatch(req, res);
162 }).listen(subscriberPort, listenAddress, (err) => {
163 if (err) {
164 console.error(err);
165 throw err;
166 }
167 console.log(`Fake Subscriber Server started on ${listenAddress}:${subscriberPort}`);
168 });
169
170 const topicService = new TopicFake();
171 http.createServer((req, res) => {
172 topicService.dispatch(req, res);
173 }).listen(topicPort, listenAddress, (err) => {
174 if (err) {
175 console.error(err);
176 throw err;
177 }
178 console.log(`Fake Topic Server started on ${listenAddress}:${topicPort}`);
179 });