nicola-framework 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +139 -494
- package/core/Core.js +85 -80
- package/core/Remote.js +7 -2
- package/database/Connection.js +2 -7
- package/database/Model.js +1 -1
- package/database/dialects/Postgres.js +8 -3
- package/dev-tools/LiveCurrent.js +2 -2
- package/middlewares/EasyCors.js +33 -14
- package/middlewares/Shadowgraph.js +2 -3
- package/package.json +6 -6
- package/security/Coherer.js +63 -38
- package/test/Coherer.test.js +73 -0
- package/test/Core.test.js +96 -0
- package/test/Router.test.js +76 -0
- package/utils/console.js +31 -0
- package/utils/expTime.js +30 -0
- package/utils/isPromise.js +7 -0
- package/database/dialects/Mysql.js +0 -0
package/core/Core.js
CHANGED
|
@@ -1,84 +1,89 @@
|
|
|
1
|
-
import http from
|
|
2
|
-
import Remote from
|
|
3
|
-
import BlackBox from
|
|
4
|
-
import Shadowgraph from
|
|
5
|
-
import Teleforce from
|
|
6
|
-
import EasyCors from
|
|
1
|
+
import http from "http";
|
|
2
|
+
import Remote from "./Remote.js";
|
|
3
|
+
import BlackBox from "../middlewares/BlackBox.js";
|
|
4
|
+
import Shadowgraph from "../middlewares/Shadowgraph.js";
|
|
5
|
+
import Teleforce from "../middlewares/Teleforce.js";
|
|
6
|
+
import EasyCors from "../middlewares/EasyCors.js";
|
|
7
7
|
class Core extends Remote {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
8
|
+
constructor() {
|
|
9
|
+
super();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
listen(port, callback) {
|
|
13
|
+
const server = http.createServer((req, res) => {
|
|
14
|
+
const myURL = new URL(req.url, "http://" + req.headers.host);
|
|
15
|
+
const pathURL = myURL.pathname;
|
|
16
|
+
const urlParams = Object.fromEntries(myURL.searchParams);
|
|
17
|
+
|
|
18
|
+
req.url = pathURL;
|
|
19
|
+
req.query = urlParams;
|
|
20
|
+
|
|
21
|
+
Shadowgraph(req, res, () => {
|
|
22
|
+
this.__addHelper(res);
|
|
23
|
+
EasyCors()(req, res, () => {
|
|
24
|
+
Teleforce(req, res, () => {
|
|
25
|
+
const done = (err) => {
|
|
26
|
+
if (!err) {
|
|
27
|
+
res.statusCode = 404;
|
|
28
|
+
res.end("Not Found");
|
|
29
|
+
} else {
|
|
30
|
+
BlackBox.ignite(err, req, res);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
if (req.headers["content-type"]?.includes("application/json")) {
|
|
34
|
+
let dataString = [];
|
|
35
|
+
let chunklenght = 0;
|
|
36
|
+
req.on("data", (chunk) => {
|
|
37
|
+
dataString.push(chunk);
|
|
38
|
+
chunklenght = chunklenght + chunk.length;
|
|
39
|
+
|
|
40
|
+
if (chunklenght > 2e6) {
|
|
41
|
+
req.pause();
|
|
42
|
+
res.statusCode = 413;
|
|
43
|
+
res.end("Request Entity Too Large");
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
req.on("end", () => {
|
|
49
|
+
try {
|
|
50
|
+
if (dataString.length > 0) {
|
|
51
|
+
const buffer = Buffer.concat(dataString).toString();
|
|
52
|
+
req.body = JSON.parse(buffer);
|
|
53
|
+
} else {
|
|
54
|
+
req.body = {};
|
|
55
|
+
}
|
|
56
|
+
} catch (error) {
|
|
57
|
+
res.statusCode = 400;
|
|
58
|
+
res.end("Bad Request: Invalid JSON");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
this.handle(req, res, done);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
req.body = {};
|
|
66
|
+
this.handle(req, res, done);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
server.listen(port, callback);
|
|
73
|
+
return server;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
__addHelper(res) {
|
|
77
|
+
res.json = (data) => {
|
|
78
|
+
res.setHeader("Content-Type", "application/json");
|
|
79
|
+
res.end(JSON.stringify(data));
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
res.send = (data) => {
|
|
83
|
+
res.setHeader("Content-Type", "text/plain");
|
|
84
|
+
res.end(data);
|
|
85
|
+
};
|
|
86
|
+
}
|
|
82
87
|
}
|
|
83
88
|
|
|
84
89
|
export default Core;
|
package/core/Remote.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { isPromise } from "../utils/isPromise.js";
|
|
2
|
+
|
|
1
3
|
|
|
2
4
|
class Remote {
|
|
3
5
|
|
|
@@ -29,7 +31,11 @@ class Remote {
|
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
try {
|
|
32
|
-
fn(req, res, internalNext);
|
|
34
|
+
let results = fn(req, res, internalNext);
|
|
35
|
+
|
|
36
|
+
if(isPromise(results)){
|
|
37
|
+
results.catch(internalNext);
|
|
38
|
+
}
|
|
33
39
|
}
|
|
34
40
|
catch (error) {
|
|
35
41
|
internalNext(error);
|
|
@@ -118,7 +124,6 @@ class Remote {
|
|
|
118
124
|
}
|
|
119
125
|
route.handler.handle(req, res, done);
|
|
120
126
|
|
|
121
|
-
console.log('Esto es un sub router')
|
|
122
127
|
} else {
|
|
123
128
|
|
|
124
129
|
route.handler(req, res, next);
|
package/database/Connection.js
CHANGED
|
@@ -19,20 +19,15 @@ class Connection {
|
|
|
19
19
|
await this.client.connect()
|
|
20
20
|
return;
|
|
21
21
|
}
|
|
22
|
-
|
|
23
|
-
await this.__connectMysql()
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
22
|
+
|
|
26
23
|
throw new Error("Driver no soportado" + DB_DRIVER)
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
|
|
30
27
|
|
|
31
|
-
static async __connectMysql(){
|
|
32
|
-
|
|
33
|
-
}
|
|
34
28
|
|
|
35
29
|
static async query(sql, params){
|
|
30
|
+
if(!this.client) throw new Error("No hay conexion activa")
|
|
36
31
|
return this.client.query(sql, params);
|
|
37
32
|
}
|
|
38
33
|
}
|
package/database/Model.js
CHANGED
|
@@ -40,10 +40,10 @@ class Postgres extends Driver {
|
|
|
40
40
|
}
|
|
41
41
|
catch (e) {
|
|
42
42
|
if (e.code === 'ERR_MODULE_NOT_FOUND') {
|
|
43
|
-
|
|
43
|
+
throw new Error('Por favor utiliza el comando npm install pg')
|
|
44
44
|
}
|
|
45
45
|
else {
|
|
46
|
-
|
|
46
|
+
throw new Error(e.message)
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
}
|
|
@@ -51,14 +51,19 @@ class Postgres extends Driver {
|
|
|
51
51
|
|
|
52
52
|
async query(sql, params) {
|
|
53
53
|
if (!this.client) {
|
|
54
|
-
|
|
54
|
+
throw new Error("Database not connected")
|
|
55
55
|
}
|
|
56
|
+
try{
|
|
56
57
|
const result = await this.client.query(sql, params);
|
|
57
58
|
return {
|
|
58
59
|
rows: result.rows,
|
|
59
60
|
count: result.rowCount
|
|
60
61
|
}
|
|
61
62
|
}
|
|
63
|
+
catch(err){
|
|
64
|
+
throw new Error(`Database Query Failed:${err.code}: ${err.message}`)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
62
67
|
|
|
63
68
|
compileSelect(builder) {
|
|
64
69
|
const stringColums = builder.columns.join(', ')
|
package/dev-tools/LiveCurrent.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'fs/promises'
|
|
2
2
|
import { spawn } from 'child_process'
|
|
3
|
-
import
|
|
3
|
+
import { green } from '../utils/console.js';
|
|
4
4
|
class LiveCurrent{
|
|
5
5
|
constructor(entryPoint){
|
|
6
6
|
this.entryPoint = entryPoint;
|
|
@@ -14,7 +14,7 @@ class LiveCurrent{
|
|
|
14
14
|
|
|
15
15
|
ignite(){
|
|
16
16
|
this.process= spawn('node', [this.entryPoint], {stdio: 'inherit'});
|
|
17
|
-
console.log(
|
|
17
|
+
console.log(green(`LiveCurrent: Iniciando Servidor...`));
|
|
18
18
|
}
|
|
19
19
|
reload(){
|
|
20
20
|
if(this.process){
|
package/middlewares/EasyCors.js
CHANGED
|
@@ -1,19 +1,38 @@
|
|
|
1
|
+
const EasyCors = (options = {}) => {
|
|
2
|
+
const allowedOrigin = options.origin || "*";
|
|
1
3
|
|
|
4
|
+
return (req, res, next) => {
|
|
5
|
+
const whitelist = Array.isArray(allowedOrigin)
|
|
6
|
+
? allowedOrigin
|
|
7
|
+
: [allowedOrigin];
|
|
8
|
+
const incomingOrigin = req.headers.origin;
|
|
9
|
+
if ((incomingOrigin && whitelist.includes(incomingOrigin)) || (whitelist.includes('*'))) {
|
|
10
|
+
if (whitelist.includes("*")) {
|
|
11
|
+
res.setHeader("Access-Control-Allow-Origin", `*`);
|
|
12
|
+
}
|
|
13
|
+
else{
|
|
14
|
+
res.setHeader("Access-Control-Allow-Origin", `${req.headers.origin}`);
|
|
15
|
+
}
|
|
16
|
+
res.setHeader(
|
|
17
|
+
"Access-Control-Allow-Methods",
|
|
18
|
+
"GET, POST, PUT, DELETE, OPTIONS, PATCH"
|
|
19
|
+
);
|
|
20
|
+
res.setHeader(
|
|
21
|
+
"Access-Control-Allow-Headers",
|
|
22
|
+
"Content-Type, Authorization"
|
|
23
|
+
);
|
|
2
24
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization')
|
|
7
|
-
|
|
8
|
-
if(req.method === 'OPTIONS'){
|
|
9
|
-
res.statusCode = 204
|
|
10
|
-
res.end()
|
|
25
|
+
if (req.method === "OPTIONS") {
|
|
26
|
+
res.statusCode = 204;
|
|
27
|
+
res.end();
|
|
11
28
|
return;
|
|
29
|
+
} else {
|
|
30
|
+
next();
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
next();
|
|
12
34
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
35
|
+
};
|
|
36
|
+
};
|
|
18
37
|
|
|
19
|
-
export default EasyCors;
|
|
38
|
+
export default EasyCors;
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import { green } from "../utils/console.js";
|
|
3
2
|
const Shadowgraph = (req, res, next) =>{
|
|
4
3
|
const inicio = Date.now()
|
|
5
4
|
res.on('finish', () =>{
|
|
6
5
|
const duracion = Date.now() - inicio
|
|
7
|
-
console.log(
|
|
6
|
+
console.log(green(`[${req.method}] ${req.url} - ${res.statusCode} ${res.statusMessage} - ${duracion}ms`))
|
|
8
7
|
})
|
|
9
8
|
next()
|
|
10
9
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nicola-framework",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "Web framework for Node.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"exports": {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"./database": "./database/index.js"
|
|
14
14
|
},
|
|
15
15
|
"scripts": {
|
|
16
|
-
"test": "
|
|
16
|
+
"test": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js"
|
|
17
17
|
},
|
|
18
18
|
"keywords": [
|
|
19
19
|
"framework",
|
|
@@ -22,12 +22,12 @@
|
|
|
22
22
|
"server",
|
|
23
23
|
"router",
|
|
24
24
|
"middleware",
|
|
25
|
-
"zero-dependency",
|
|
26
25
|
"orm"
|
|
27
26
|
],
|
|
28
27
|
"author": "Erick Mauricio Tiznado Rodriguez",
|
|
29
28
|
"license": "MIT",
|
|
30
|
-
"
|
|
31
|
-
"
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"jest": "^30.2.0",
|
|
31
|
+
"supertest": "^7.2.2"
|
|
32
32
|
}
|
|
33
33
|
}
|
package/security/Coherer.js
CHANGED
|
@@ -1,53 +1,78 @@
|
|
|
1
|
-
import crypto from
|
|
2
|
-
import Regulator from
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
import Regulator from "./Regulator.js";
|
|
3
|
+
import { getExpTime } from "../utils/expTime.js";
|
|
3
4
|
|
|
4
|
-
class Coherer{
|
|
5
|
-
|
|
5
|
+
class Coherer {
|
|
6
|
+
constructor() {}
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
static codec(jsonData) {
|
|
9
|
+
const dataString = JSON.stringify(jsonData);
|
|
10
|
+
const buffer = Buffer.from(dataString);
|
|
11
|
+
return buffer.toString("base64url");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
static sign(Payload, options) {
|
|
15
|
+
const SECRET = process.env.NICOLA_SECRET
|
|
16
|
+
if (!SECRET)
|
|
17
|
+
throw new Error("Please configure, NICOLA_SECRET in the .env file");
|
|
18
|
+
|
|
19
|
+
let payloadB64 = "";
|
|
9
20
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
21
|
+
if ("expiresIn" in options) {
|
|
22
|
+
const time = getExpTime(options.expiresIn);
|
|
23
|
+
const newPayload = { ...Payload, exp: time };
|
|
24
|
+
payloadB64 = this.codec(newPayload);
|
|
25
|
+
} else {
|
|
26
|
+
throw new Error("Expire time invalid");
|
|
14
27
|
}
|
|
15
28
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
const headerB64 = this.codec(header)
|
|
29
|
+
const header = {
|
|
30
|
+
alg: "HS256",
|
|
31
|
+
typ: "JWT",
|
|
32
|
+
};
|
|
33
|
+
const headerB64 = this.codec(header);
|
|
23
34
|
|
|
24
|
-
|
|
35
|
+
const data = headerB64 + "." + payloadB64;
|
|
25
36
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
37
|
+
const signature = crypto
|
|
38
|
+
.createHmac("sha256", SECRET)
|
|
39
|
+
.update(data)
|
|
40
|
+
.digest("base64url");
|
|
29
41
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
static verify(data){
|
|
33
|
-
const [headerB64, payloadB64, signature] = data.token.split('.');
|
|
42
|
+
return data + "." + signature;
|
|
43
|
+
}
|
|
34
44
|
|
|
35
|
-
|
|
45
|
+
static verify(token) {
|
|
46
|
+
const SECRET = process.env.NICOLA_SECRET
|
|
47
|
+
if (!SECRET)
|
|
48
|
+
throw new Error("Please configure, NICOLA_SECRET in the .env file");
|
|
49
|
+
const [headerB64, payloadB64, signature] = token.split(".");
|
|
36
50
|
|
|
37
|
-
|
|
38
|
-
.update(dataToCheck)
|
|
39
|
-
.digest('base64url')
|
|
51
|
+
const dataToCheck = headerB64 + "." + payloadB64;
|
|
40
52
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
53
|
+
const signatureToChecks = crypto
|
|
54
|
+
.createHmac("sha256", SECRET)
|
|
55
|
+
.update(dataToCheck)
|
|
56
|
+
.digest("base64url");
|
|
57
|
+
|
|
58
|
+
if (signature === signatureToChecks) {
|
|
59
|
+
let decodedPayload = Buffer.from(payloadB64, "base64url").toString(
|
|
60
|
+
"utf-8"
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
decodedPayload = JSON.parse(decodedPayload);
|
|
64
|
+
if ("exp" in decodedPayload) {
|
|
65
|
+
const datenow = Date.now() / 1000;
|
|
66
|
+
|
|
67
|
+
if (datenow > decodedPayload.exp) {
|
|
68
|
+
throw new Error("Token Expired");
|
|
48
69
|
}
|
|
70
|
+
}
|
|
71
|
+
return decodedPayload;
|
|
72
|
+
} else {
|
|
73
|
+
throw new Error("Token Invalido");
|
|
49
74
|
}
|
|
75
|
+
}
|
|
50
76
|
}
|
|
51
77
|
|
|
52
|
-
|
|
53
|
-
export default Coherer;
|
|
78
|
+
export default Coherer;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import Coherer from '../security/Coherer.js'; // Ajusta la ruta según tu estructura
|
|
2
|
+
// Nota: Jest inyecta 'describe', 'test', 'expect' globalmente, no hace falta importarlos.
|
|
3
|
+
|
|
4
|
+
describe('🛡️ Módulo de Seguridad (Coherer)', () => {
|
|
5
|
+
|
|
6
|
+
// Antes de todos los tests, configuramos el entorno
|
|
7
|
+
beforeAll(() => {
|
|
8
|
+
process.env.NICOLA_SECRET = 'test_secret_key_123';
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
// Limpiamos después (buena práctica)
|
|
12
|
+
afterAll(() => {
|
|
13
|
+
delete process.env.NICOLA_SECRET;
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe('Happy Path (Lo que debe salir bien)', () => {
|
|
17
|
+
|
|
18
|
+
test('Debe firmar y verificar un token correctamente', () => {
|
|
19
|
+
const payload = { userId: 1, role: 'admin' };
|
|
20
|
+
const options = { expiresIn: '1h' };
|
|
21
|
+
|
|
22
|
+
// 1. Firmar
|
|
23
|
+
const token = Coherer.sign(payload, options);
|
|
24
|
+
|
|
25
|
+
// Verificamos que sea un string y tenga 3 partes (header.payload.signature)
|
|
26
|
+
expect(typeof token).toBe('string');
|
|
27
|
+
expect(token.split('.')).toHaveLength(3);
|
|
28
|
+
|
|
29
|
+
// 2. Verificar
|
|
30
|
+
const decoded = Coherer.verify(token);
|
|
31
|
+
|
|
32
|
+
// Verificamos que la data sea la misma
|
|
33
|
+
expect(decoded.userId).toBe(payload.userId);
|
|
34
|
+
expect(decoded.role).toBe(payload.role);
|
|
35
|
+
|
|
36
|
+
// Verificamos que se haya agregado la expiración
|
|
37
|
+
expect(decoded).toHaveProperty('exp');
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('Sad Path (Errores esperados)', () => {
|
|
42
|
+
|
|
43
|
+
test('Debe fallar si no se define una expiración', () => {
|
|
44
|
+
const payload = { userId: 1 };
|
|
45
|
+
|
|
46
|
+
// En Jest, para probar errores, envolvemos la llamada en una función anónima () =>
|
|
47
|
+
expect(() => {
|
|
48
|
+
Coherer.sign(payload, {});
|
|
49
|
+
|
|
50
|
+
}).toThrow('Expire time invalid');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('Debe fallar si el token es basura', () => {
|
|
54
|
+
const tokenBasura = 'esto.no.es.valido';
|
|
55
|
+
|
|
56
|
+
expect(() => {
|
|
57
|
+
Coherer.verify(tokenBasura);
|
|
58
|
+
}).toThrow(); // Esperamos cualquier error (Firma inválida, malformado, etc.)
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('Debe fallar si la firma ha sido manipulada', () => {
|
|
62
|
+
const tokenReal = Coherer.sign({ id: 1 }, { expiresIn: '1h' });
|
|
63
|
+
const partes = tokenReal.split('.');
|
|
64
|
+
|
|
65
|
+
// Hack: Cambiamos la firma (última parte) por otra cosa
|
|
66
|
+
const tokenFalso = `${partes[0]}.${partes[1]}.FIRMA_FALSA`;
|
|
67
|
+
|
|
68
|
+
expect(() => {
|
|
69
|
+
Coherer.verify(tokenFalso);
|
|
70
|
+
}).toThrow('Token Invalido');
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import request from "supertest";
|
|
2
|
+
import Core from "../core/Core.js";
|
|
3
|
+
|
|
4
|
+
describe("🧠 Core System (Core.js)", () => {
|
|
5
|
+
let app;
|
|
6
|
+
let server;
|
|
7
|
+
|
|
8
|
+
beforeAll((done) => {
|
|
9
|
+
app = new Core();
|
|
10
|
+
|
|
11
|
+
app.get("/ping", (req, res) => {
|
|
12
|
+
res.statusCode = 200;
|
|
13
|
+
res.end("pong");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
app.get("/query", (req, res) => {
|
|
17
|
+
res.json({ query: req.query });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
app.post("/json", (req, res) => {
|
|
21
|
+
res.json({ body: req.body });
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
server = app.listen(0, done);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
afterAll((done) => {
|
|
28
|
+
server.close(done);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("GET /ping responde 200", async () => {
|
|
32
|
+
const res = await request(server).get("/ping");
|
|
33
|
+
expect(res.statusCode).toBe(200);
|
|
34
|
+
expect(res.text).toBe("pong");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("Aplica headers de Teleforce", async () => {
|
|
38
|
+
const res = await request(server).get("/ping");
|
|
39
|
+
expect(res.headers["x-content-type-options"]).toBe("nosniff");
|
|
40
|
+
expect(res.headers["x-frame-options"]).toBe("Deny");
|
|
41
|
+
expect(res.headers["x-xss-protection"]).toBe("1");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("Parsea querystring y lo expone en req.query", async () => {
|
|
45
|
+
const res = await request(server).get("/query?x=1&y=hola");
|
|
46
|
+
expect(res.statusCode).toBe(200);
|
|
47
|
+
expect(res.body).toEqual({ query: { x: "1", y: "hola" } });
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("POST JSON válido -> req.body parseado", async () => {
|
|
51
|
+
const res = await request(server)
|
|
52
|
+
.post("/json")
|
|
53
|
+
.set("Content-Type", "application/json; charset=utf-8")
|
|
54
|
+
.send(JSON.stringify({ a: 1 }));
|
|
55
|
+
|
|
56
|
+
expect(res.statusCode).toBe(200);
|
|
57
|
+
expect(res.body).toEqual({ body: { a: 1 } });
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("POST JSON inválido -> 400", async () => {
|
|
61
|
+
const res = await request(server)
|
|
62
|
+
.post("/json")
|
|
63
|
+
.set("Content-Type", "application/json")
|
|
64
|
+
.send("{invalid");
|
|
65
|
+
|
|
66
|
+
expect(res.statusCode).toBe(400);
|
|
67
|
+
expect(res.text).toBe("Bad Request: Invalid JSON");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("Sin Content-Type -> req.body = {}", async () => {
|
|
71
|
+
const res = await request(server).post("/json").send("no-json");
|
|
72
|
+
expect(res.statusCode).toBe(200);
|
|
73
|
+
expect(res.body).toEqual({ body: {} });
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("OPTIONS responde 204 y headers CORS", async () => {
|
|
77
|
+
const res = await request(server).options("/ping");
|
|
78
|
+
expect(res.statusCode).toBe(204);
|
|
79
|
+
expect(res.headers["access-control-allow-origin"]).toBe("*");
|
|
80
|
+
expect(res.headers["access-control-allow-methods"]).toBe(
|
|
81
|
+
"GET, POST, PUT, DELETE, OPTIONS, PATCH"
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("Body > ~2MB -> 413", async () => {
|
|
86
|
+
const big = "{\"x\":\"" + "a".repeat(2_000_001) + "\"}";
|
|
87
|
+
|
|
88
|
+
const res = await request(server)
|
|
89
|
+
.post("/json")
|
|
90
|
+
.set("Content-Type", "application/json")
|
|
91
|
+
.send(big);
|
|
92
|
+
|
|
93
|
+
expect(res.statusCode).toBe(413);
|
|
94
|
+
expect(res.text).toBe("Request Entity Too Large");
|
|
95
|
+
});
|
|
96
|
+
});
|