Skip to content

Commit 2e6b59c

Browse files
authored
Merge pull request #1486 from siddharthbaleja7/test/cors-unit-tests
test(binding-http): add unit tests for CORS behavior
2 parents 0cdf192 + 1ebd2e7 commit 2e6b59c

File tree

1 file changed

+252
-0
lines changed

1 file changed

+252
-0
lines changed
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
/********************************************************************************
2+
* Copyright (c) 2026 Contributors to the Eclipse Foundation
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information regarding copyright ownership.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Eclipse Public License v. 2.0 which is available at
9+
* http://www.eclipse.org/legal/epl-2.0, or the W3C Software Notice and
10+
* Document License (2015-05-13) which is available at
11+
* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document.
12+
*
13+
* SPDX-License-Identifier: EPL-2.0 OR W3C-20150513
14+
********************************************************************************/
15+
import { suite, test } from "@testdeck/mocha";
16+
import { expect, should } from "chai";
17+
import fetch from "node-fetch";
18+
import HttpServer from "../src/http-server";
19+
import Servient, { ExposedThing } from "@node-wot/core";
20+
21+
// should must be called to augment all variables
22+
should();
23+
24+
@suite("HTTP Server CORS")
25+
class HttpServerCorsTest {
26+
private httpServer!: HttpServer;
27+
private servient!: Servient;
28+
private thing!: ExposedThing;
29+
30+
async before() {
31+
this.servient = new Servient();
32+
this.httpServer = new HttpServer({ port: 0 });
33+
await this.httpServer.start(this.servient);
34+
}
35+
36+
async after() {
37+
await this.httpServer.stop();
38+
}
39+
40+
@test async "should handle CORS with no security (nosec)"() {
41+
this.thing = new ExposedThing(this.servient, {
42+
title: "TestThingNoSec",
43+
properties: {
44+
test: {
45+
type: "string",
46+
forms: [],
47+
},
48+
},
49+
});
50+
51+
this.thing.setPropertyReadHandler("test", () => Promise.resolve("test-value"));
52+
53+
await this.httpServer.expose(this.thing);
54+
55+
const uri = `http://localhost:${this.httpServer.getPort()}/testthingnosec/properties/test`;
56+
const response = await fetch(uri, {
57+
headers: {
58+
Origin: "http://example.com",
59+
},
60+
});
61+
62+
expect(response.status).to.equal(200);
63+
expect(response.headers.get("Access-Control-Allow-Origin")).to.equal("*");
64+
expect(response.headers.has("Access-Control-Allow-Credentials")).to.be.false;
65+
}
66+
67+
@test async "should handle CORS with basic security (401 response)"() {
68+
await this.httpServer.stop();
69+
70+
this.httpServer = new HttpServer({
71+
port: 0,
72+
security: [{ scheme: "basic" }],
73+
});
74+
await this.httpServer.start(this.servient);
75+
76+
this.thing = new ExposedThing(this.servient, {
77+
title: "TestThingBasic",
78+
securityDefinitions: {
79+
basic_sc: { scheme: "basic" },
80+
},
81+
security: ["basic_sc"],
82+
properties: {
83+
test: {
84+
type: "string",
85+
forms: [],
86+
},
87+
},
88+
});
89+
90+
await this.httpServer.expose(this.thing);
91+
92+
const uri = `http://localhost:${this.httpServer.getPort()}/testthingbasic/properties/test`;
93+
const response = await fetch(uri, {
94+
headers: {
95+
Origin: "http://example.com",
96+
},
97+
});
98+
99+
expect(response.status).to.equal(401);
100+
expect(response.headers.get("Access-Control-Allow-Origin")).to.equal("http://example.com");
101+
expect(response.headers.get("Access-Control-Allow-Credentials")).to.equal("true");
102+
}
103+
104+
@test async "should handle CORS with basic security (200 response)"() {
105+
await this.httpServer.stop();
106+
107+
this.httpServer = new HttpServer({
108+
port: 0,
109+
security: [{ scheme: "basic" }],
110+
});
111+
await this.httpServer.start(this.servient);
112+
113+
this.thing = new ExposedThing(this.servient, {
114+
title: "TestThingBasic200",
115+
securityDefinitions: {
116+
basic_sc: { scheme: "basic" },
117+
},
118+
security: ["basic_sc"],
119+
id: "urn:test:thing:basic:200",
120+
properties: {
121+
test: {
122+
type: "string",
123+
forms: [],
124+
},
125+
},
126+
});
127+
128+
this.thing.setPropertyReadHandler("test", () => Promise.resolve("success"));
129+
130+
this.servient.addCredentials({
131+
"urn:test:thing:basic:200": {
132+
username: "user",
133+
password: "password",
134+
},
135+
});
136+
137+
await this.httpServer.expose(this.thing);
138+
139+
const uri = `http://localhost:${this.httpServer.getPort()}/urn:test:thing:basic:200/properties/test`;
140+
141+
const auth = Buffer.from("user:password").toString("base64");
142+
143+
const response = await fetch(uri, {
144+
headers: {
145+
Origin: "http://example.com",
146+
Authorization: `Basic ${auth}`,
147+
},
148+
});
149+
150+
expect(response.status).to.equal(200);
151+
expect(response.headers.get("Access-Control-Allow-Origin")).to.equal("http://example.com");
152+
expect(response.headers.get("Access-Control-Allow-Credentials")).to.equal("true");
153+
expect(await response.json()).to.equal("success");
154+
}
155+
156+
@test async "should handle CORS preflight for basic security"() {
157+
await this.httpServer.stop();
158+
159+
this.httpServer = new HttpServer({
160+
port: 0,
161+
security: [{ scheme: "basic" }],
162+
});
163+
await this.httpServer.start(this.servient);
164+
165+
this.thing = new ExposedThing(this.servient, {
166+
title: "TestThingPreflight",
167+
securityDefinitions: {
168+
basic_sc: { scheme: "basic" },
169+
},
170+
security: ["basic_sc"],
171+
properties: {
172+
test: {
173+
type: "string",
174+
forms: [],
175+
},
176+
},
177+
});
178+
179+
await this.httpServer.expose(this.thing);
180+
181+
const uri = `http://localhost:${this.httpServer.getPort()}/testthingpreflight/properties/test`;
182+
const response = await fetch(uri, {
183+
method: "OPTIONS",
184+
headers: {
185+
Origin: "http://example.com",
186+
"Access-Control-Request-Method": "GET",
187+
},
188+
});
189+
190+
expect(response.status).to.equal(200);
191+
expect(response.headers.get("Access-Control-Allow-Origin")).to.equal("http://example.com");
192+
expect(response.headers.get("Access-Control-Allow-Credentials")).to.equal("true");
193+
const methods = response.headers.get("Access-Control-Allow-Methods");
194+
expect(methods).to.contain("GET");
195+
expect(methods).to.contain("OPTIONS");
196+
}
197+
198+
@test async "should handle CORS for write property (PUT)"() {
199+
this.thing = new ExposedThing(this.servient, {
200+
title: "TestThingWrite",
201+
properties: {
202+
test: {
203+
type: "string",
204+
forms: [],
205+
},
206+
},
207+
});
208+
209+
this.thing.setPropertyWriteHandler("test", () => Promise.resolve(undefined));
210+
211+
await this.httpServer.expose(this.thing);
212+
213+
const uri = `http://localhost:${this.httpServer.getPort()}/testthingwrite/properties/test`;
214+
const response = await fetch(uri, {
215+
method: "PUT",
216+
body: JSON.stringify("new-value"),
217+
headers: {
218+
Origin: "http://example.com",
219+
"Content-Type": "application/json",
220+
},
221+
});
222+
223+
expect(response.status).to.equal(204);
224+
expect(response.headers.get("Access-Control-Allow-Origin")).to.equal("*");
225+
}
226+
227+
@test async "should handle CORS for invoke action (POST)"() {
228+
this.thing = new ExposedThing(this.servient, {
229+
title: "TestThingAction",
230+
actions: {
231+
test: {
232+
forms: [],
233+
},
234+
},
235+
});
236+
237+
this.thing.setActionHandler("test", () => Promise.resolve(undefined));
238+
239+
await this.httpServer.expose(this.thing);
240+
241+
const uri = `http://localhost:${this.httpServer.getPort()}/testthingaction/actions/test`;
242+
const response = await fetch(uri, {
243+
method: "POST",
244+
headers: {
245+
Origin: "http://example.com",
246+
},
247+
});
248+
249+
expect(response.status).to.equal(204); // Action without output returns 204
250+
expect(response.headers.get("Access-Control-Allow-Origin")).to.equal("*");
251+
}
252+
}

0 commit comments

Comments
 (0)