express-genix 4.3.0 → 4.4.1
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/index.js +1 -1
- package/lib/features.js +5 -0
- package/package.json +1 -1
- package/templates/config/queue.js.ejs +1 -1
- package/templates/config/redis.js.ejs +1 -1
- package/templates/controllers/aiController.js.ejs +7 -7
- package/templates/controllers/authController.js.ejs +4 -4
- package/templates/core/Dockerfile.ejs +16 -6
- package/templates/core/README.md.ejs +12 -12
- package/templates/core/docker-compose.yml.ejs +5 -5
- package/templates/core/package.json.ejs +5 -2
- package/templates/jobs/worker.js.ejs +1 -1
- package/templates/mcp/server.js.ejs +3 -3
- package/templates/middleware/auth.js.ejs +2 -14
- package/templates/routes/aiRoutes.js.ejs +4 -13
- package/templates/routes/index.js.ejs +1 -1
- package/templates/services/aiService.js.ejs +1 -1
- package/templates/services/emailService.js.ejs +1 -1
- package/templates/tests/example.test.js.ejs +3 -3
- package/templates/tests/setup.js.ejs +1 -1
package/index.js
CHANGED
|
@@ -54,7 +54,7 @@ async function promptCodaExtension() {
|
|
|
54
54
|
} catch {
|
|
55
55
|
console.log('\n⚠️ Could not install automatically. Install it manually:');
|
|
56
56
|
console.log(' 1. Download from: https://github.com/LambdaAI001/coda/releases');
|
|
57
|
-
console.log(' 2. Then run: code --install-extension coda-ai-0.
|
|
57
|
+
console.log(' 2. Then run: code --install-extension coda-ai-0.2.0.vsix');
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|
package/lib/features.js
CHANGED
|
@@ -242,6 +242,11 @@ const inferConfig = (projectDir, pkg) => {
|
|
|
242
242
|
hasGraphQL: !!(pkg.dependencies && pkg.dependencies['@apollo/server']),
|
|
243
243
|
hasMCP: !!(pkg.dependencies && pkg.dependencies['@modelcontextprotocol/sdk']),
|
|
244
244
|
hasAI: !!(pkg.dependencies && pkg.dependencies['@langchain/core']),
|
|
245
|
+
aiProvider: pkg.dependencies && pkg.dependencies['@langchain/openai'] ? 'openai'
|
|
246
|
+
: pkg.dependencies && pkg.dependencies['@langchain/anthropic'] ? 'anthropic'
|
|
247
|
+
: pkg.dependencies && pkg.dependencies['@langchain/google-genai'] ? 'gemini'
|
|
248
|
+
: pkg.dependencies && pkg.dependencies['@langchain/ollama'] ? 'ollama'
|
|
249
|
+
: 'openai',
|
|
245
250
|
logger: pkg.dependencies && pkg.dependencies.pino ? 'pino' : 'winston',
|
|
246
251
|
};
|
|
247
252
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "express-genix",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.4.1",
|
|
4
4
|
"description": "Production-grade CLI to generate Express apps with JWT, RBAC, GraphQL, TypeScript, Prisma, MongoDB, PostgreSQL, file uploads, email, background jobs, and more",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -10,7 +10,7 @@ const chatHandler = async (req, res, next) => {
|
|
|
10
10
|
const { message, systemPrompt, history, model, temperature, maxTokens } = req.body;
|
|
11
11
|
|
|
12
12
|
if (!message) {
|
|
13
|
-
return
|
|
13
|
+
return error(res, 'Message is required', 400);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
const result = await aiService.chat(message, {
|
|
@@ -21,7 +21,7 @@ const chatHandler = async (req, res, next) => {
|
|
|
21
21
|
maxTokens,
|
|
22
22
|
});
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
return success(res, result);
|
|
25
25
|
} catch (err) {
|
|
26
26
|
next(err);
|
|
27
27
|
}
|
|
@@ -35,7 +35,7 @@ const streamHandler = async (req, res, next) => {
|
|
|
35
35
|
const { message, systemPrompt, history, model, temperature, maxTokens } = req.body;
|
|
36
36
|
|
|
37
37
|
if (!message) {
|
|
38
|
-
return
|
|
38
|
+
return error(res, 'Message is required', 400);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
res.setHeader('Content-Type', 'text/event-stream');
|
|
@@ -69,11 +69,11 @@ const chainHandler = async (req, res, next) => {
|
|
|
69
69
|
const { template, variables, model } = req.body;
|
|
70
70
|
|
|
71
71
|
if (!template) {
|
|
72
|
-
return
|
|
72
|
+
return error(res, 'Template is required', 400);
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
const result = await aiService.chain(template, variables || {}, { model });
|
|
76
|
-
|
|
76
|
+
return success(res, { content: result });
|
|
77
77
|
} catch (err) {
|
|
78
78
|
next(err);
|
|
79
79
|
}
|
|
@@ -87,11 +87,11 @@ const agentHandler = async (req, res, next) => {
|
|
|
87
87
|
const { message, systemPrompt, model } = req.body;
|
|
88
88
|
|
|
89
89
|
if (!message) {
|
|
90
|
-
return
|
|
90
|
+
return error(res, 'Message is required', 400);
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
const result = await runAgent(message, { systemPrompt, model });
|
|
94
|
-
|
|
94
|
+
return success(res, result);
|
|
95
95
|
} catch (err) {
|
|
96
96
|
next(err);
|
|
97
97
|
}
|
|
@@ -65,7 +65,7 @@ const refreshToken = async (req, res, next) => {
|
|
|
65
65
|
throw new AppError('Refresh token is required', 400);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
if (authService.isTokenBlacklisted(token)) {
|
|
68
|
+
if (await authService.isTokenBlacklisted(token)) {
|
|
69
69
|
throw new AppError('Token has been revoked', 401);
|
|
70
70
|
}
|
|
71
71
|
|
|
@@ -77,7 +77,7 @@ const refreshToken = async (req, res, next) => {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
// Blacklist old refresh token (rotation)
|
|
80
|
-
authService.blacklistToken(token);
|
|
80
|
+
await authService.blacklistToken(token);
|
|
81
81
|
|
|
82
82
|
const tokens = authService.generateTokens(user);
|
|
83
83
|
return success(res, tokens);
|
|
@@ -92,12 +92,12 @@ const logout = async (req, res, next) => {
|
|
|
92
92
|
const accessToken = authHeader && authHeader.split(' ')[1];
|
|
93
93
|
|
|
94
94
|
if (accessToken) {
|
|
95
|
-
authService.blacklistToken(accessToken);
|
|
95
|
+
await authService.blacklistToken(accessToken);
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
const { refreshToken: token } = req.body;
|
|
99
99
|
if (token) {
|
|
100
|
-
authService.blacklistToken(token);
|
|
100
|
+
await authService.blacklistToken(token);
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
return success(res, { message: 'Logout successful' });
|
|
@@ -1,4 +1,16 @@
|
|
|
1
|
-
FROM node:20-alpine
|
|
1
|
+
FROM node:20-alpine<% if (isTypescript) { %> AS builder
|
|
2
|
+
|
|
3
|
+
# Build stage for TypeScript
|
|
4
|
+
WORKDIR /app
|
|
5
|
+
COPY package*.json ./
|
|
6
|
+
<% if (isPrisma) { %>COPY prisma ./prisma/<% } %>
|
|
7
|
+
RUN npm ci
|
|
8
|
+
<% if (isPrisma) { %>RUN npx prisma generate<% } %>
|
|
9
|
+
COPY tsconfig.json ./
|
|
10
|
+
COPY src ./src/
|
|
11
|
+
RUN npx tsc
|
|
12
|
+
|
|
13
|
+
FROM node:20-alpine<% } %>
|
|
2
14
|
|
|
3
15
|
# Set working directory
|
|
4
16
|
WORKDIR /app
|
|
@@ -9,16 +21,14 @@ COPY package*.json ./
|
|
|
9
21
|
# Copy Prisma schema for generation
|
|
10
22
|
COPY prisma ./prisma/
|
|
11
23
|
<% } %>
|
|
12
|
-
# Install dependencies
|
|
24
|
+
# Install production dependencies
|
|
13
25
|
RUN npm ci --only=production && npm cache clean --force
|
|
14
26
|
<% if (isPrisma) { %>
|
|
15
27
|
# Generate Prisma client
|
|
16
28
|
RUN npx prisma generate
|
|
17
29
|
<% } %><% if (isTypescript) { %>
|
|
18
|
-
# Copy
|
|
19
|
-
COPY
|
|
20
|
-
COPY src ./src/
|
|
21
|
-
RUN npx tsc
|
|
30
|
+
# Copy compiled output from builder
|
|
31
|
+
COPY --from=builder /app/dist ./dist/
|
|
22
32
|
<% } else { %>
|
|
23
33
|
# Copy application code
|
|
24
34
|
COPY . .
|
|
@@ -126,27 +126,27 @@ Interactive docs available at [http://localhost:3000/api-docs](http://localhost:
|
|
|
126
126
|
|
|
127
127
|
| Method | Endpoint | Description |
|
|
128
128
|
|--------|----------|-------------|
|
|
129
|
-
| POST | `/api
|
|
130
|
-
| POST | `/api
|
|
131
|
-
| POST | `/api
|
|
132
|
-
| POST | `/api
|
|
129
|
+
| POST | `/api<% if (hasApiVersioning) { %>/v1<% } %>/auth/register` | Register a new user |
|
|
130
|
+
| POST | `/api<% if (hasApiVersioning) { %>/v1<% } %>/auth/login` | Login and receive tokens |
|
|
131
|
+
| POST | `/api<% if (hasApiVersioning) { %>/v1<% } %>/auth/refresh` | Refresh access token |
|
|
132
|
+
| POST | `/api<% if (hasApiVersioning) { %>/v1<% } %>/auth/logout` | Logout (blacklist token) |
|
|
133
133
|
|
|
134
134
|
### Users (protected)
|
|
135
135
|
|
|
136
136
|
| Method | Endpoint | Description |
|
|
137
137
|
|--------|----------|-------------|
|
|
138
|
-
| GET | `/api
|
|
139
|
-
| PUT | `/api
|
|
140
|
-
| DELETE | `/api
|
|
138
|
+
| GET | `/api<% if (hasApiVersioning) { %>/v1<% } %>/users/profile` | Get current user profile |
|
|
139
|
+
| PUT | `/api<% if (hasApiVersioning) { %>/v1<% } %>/users/profile` | Update profile |
|
|
140
|
+
| DELETE | `/api<% if (hasApiVersioning) { %>/v1<% } %>/users/profile` | Delete account |
|
|
141
141
|
<% } else { %>### Examples
|
|
142
142
|
|
|
143
143
|
| Method | Endpoint | Description |
|
|
144
144
|
|--------|----------|-------------|
|
|
145
|
-
| GET | `/api
|
|
146
|
-
| GET | `/api
|
|
147
|
-
| POST | `/api
|
|
148
|
-
| PUT | `/api
|
|
149
|
-
| DELETE | `/api
|
|
145
|
+
| GET | `/api<% if (hasApiVersioning) { %>/v1<% } %>/examples` | List all (paginated) |
|
|
146
|
+
| GET | `/api<% if (hasApiVersioning) { %>/v1<% } %>/examples/:id` | Get by ID |
|
|
147
|
+
| POST | `/api<% if (hasApiVersioning) { %>/v1<% } %>/examples` | Create |
|
|
148
|
+
| PUT | `/api<% if (hasApiVersioning) { %>/v1<% } %>/examples/:id` | Update |
|
|
149
|
+
| DELETE | `/api<% if (hasApiVersioning) { %>/v1<% } %>/examples/:id` | Delete |
|
|
150
150
|
<% } %>
|
|
151
151
|
### Health
|
|
152
152
|
|
|
@@ -11,7 +11,7 @@ services:
|
|
|
11
11
|
env_file:
|
|
12
12
|
- .env<% if (hasDatabase) { %>
|
|
13
13
|
depends_on:
|
|
14
|
-
- <%= db === 'mongodb' ? 'mongodb' : 'postgres' %><% } %><% if (hasRedis) { %>
|
|
14
|
+
- <%= db === 'mongodb' ? 'mongodb' : 'postgres' %><% } %><% if (hasRedis || hasBackgroundJobs) { %>
|
|
15
15
|
- redis<% } %>
|
|
16
16
|
restart: unless-stopped
|
|
17
17
|
networks:
|
|
@@ -27,7 +27,7 @@ services:
|
|
|
27
27
|
- mongodb_data:/data/db
|
|
28
28
|
restart: unless-stopped
|
|
29
29
|
networks:
|
|
30
|
-
- app-network<% } else if (db === 'postgresql') { %> postgres:
|
|
30
|
+
- app-network<% } else if (db === 'postgresql' || db === 'prisma') { %> postgres:
|
|
31
31
|
image: postgres:15-alpine
|
|
32
32
|
ports:
|
|
33
33
|
- "5432:5432"
|
|
@@ -41,7 +41,7 @@ services:
|
|
|
41
41
|
networks:
|
|
42
42
|
- app-network<% } %>
|
|
43
43
|
|
|
44
|
-
<% if (hasRedis) { %> redis:
|
|
44
|
+
<% if (hasRedis || hasBackgroundJobs) { %> redis:
|
|
45
45
|
image: redis:7-alpine
|
|
46
46
|
ports:
|
|
47
47
|
- "6379:6379"
|
|
@@ -51,9 +51,9 @@ services:
|
|
|
51
51
|
networks:
|
|
52
52
|
- app-network
|
|
53
53
|
<% } %>
|
|
54
|
-
<% if (hasDatabase || hasRedis) { %>volumes:<% } %>
|
|
54
|
+
<% if (hasDatabase || hasRedis || hasBackgroundJobs) { %>volumes:<% } %>
|
|
55
55
|
<% if (hasDatabase) { %> <%= db === 'mongodb' ? 'mongodb_data:' : 'postgres_data:' %><% } %>
|
|
56
|
-
<% if (hasRedis) { %> redis_data:<% } %>
|
|
56
|
+
<% if (hasRedis || hasBackgroundJobs) { %> redis_data:<% } %>
|
|
57
57
|
|
|
58
58
|
networks:
|
|
59
59
|
app-network:
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"db:migrate:undo": "npx sequelize-cli db:migrate:undo",
|
|
20
20
|
"db:seed": "npx sequelize-cli db:seed:all",
|
|
21
21
|
"db:create": "npx sequelize-cli db:create"<% } %><% if (hasMCP) { %>,
|
|
22
|
-
"mcp:start": "node src/mcp/server.js"<% } %>
|
|
22
|
+
"mcp:start": "<% if (isTypescript) { %>tsx src/mcp/server.ts<% } else { %>node src/mcp/server.js<% } %>"<% } %>
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"express": "^4.21.0",
|
|
@@ -73,7 +73,10 @@
|
|
|
73
73
|
"@types/swagger-jsdoc": "^6.0.4",
|
|
74
74
|
"@types/swagger-ui-express": "^4.1.6"<% } %><% if (hasRequestId) { %>,
|
|
75
75
|
"@types/uuid": "^10.0.0"<% } %>,<% } %>
|
|
76
|
-
"jest": "^29.7.0"
|
|
76
|
+
"jest": "^29.7.0",<% if (isTypescript) { %>
|
|
77
|
+
"ts-jest": "^29.2.0",
|
|
78
|
+
"@typescript-eslint/parser": "^7.0.0",
|
|
79
|
+
"@typescript-eslint/eslint-plugin": "^7.0.0",<% } %>
|
|
77
80
|
"supertest": "^7.0.0",
|
|
78
81
|
"eslint": "^8.57.0",
|
|
79
82
|
"eslint-config-airbnb-base": "^15.0.0",
|
|
@@ -5,7 +5,7 @@ const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio
|
|
|
5
5
|
const { z } = require('zod');
|
|
6
6
|
<% if (hasDatabase) { %>const db = require('../config/database');
|
|
7
7
|
<% } %><% if (hasAuth) { %>const userService = require('../services/userService');
|
|
8
|
-
<% } %><% if (!hasAuth
|
|
8
|
+
<% } %><% if (!hasAuth) { %>const exampleService = require('../services/exampleService');
|
|
9
9
|
<% } %><% if (hasEmail) { %>const emailService = require('../services/emailService');
|
|
10
10
|
<% } %>
|
|
11
11
|
|
|
@@ -82,7 +82,7 @@ server.tool(
|
|
|
82
82
|
limit: z.number().optional().describe('Items per page (default: 20)'),
|
|
83
83
|
},
|
|
84
84
|
async ({ page = 1, limit = 20 }) => {
|
|
85
|
-
const result = await exampleService.
|
|
85
|
+
const result = await exampleService.getAllExamples(page, limit);
|
|
86
86
|
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
87
87
|
}
|
|
88
88
|
);
|
|
@@ -95,7 +95,7 @@ server.tool(
|
|
|
95
95
|
description: z.string().optional().describe('Item description'),
|
|
96
96
|
},
|
|
97
97
|
async ({ title, description }) => {
|
|
98
|
-
const item = await exampleService.
|
|
98
|
+
const item = await exampleService.createExample({ title, description });
|
|
99
99
|
return { content: [{ type: 'text', text: JSON.stringify(item, null, 2) }] };
|
|
100
100
|
}
|
|
101
101
|
);
|
|
@@ -27,7 +27,7 @@ const authenticateToken = <% if (hasRedis) { %>async <% } %>(req, res, next) =>
|
|
|
27
27
|
|
|
28
28
|
jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
|
|
29
29
|
if (err) {
|
|
30
|
-
return next(new AppError('Invalid or expired token',
|
|
30
|
+
return next(new AppError('Invalid or expired token', 401));
|
|
31
31
|
}
|
|
32
32
|
req.user = decoded;
|
|
33
33
|
next();
|
|
@@ -65,16 +65,4 @@ const optionalAuth = <% if (hasRedis) { %>async <% } %>(req, res, next) => {
|
|
|
65
65
|
next();
|
|
66
66
|
};
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
return (req, res, next) => {
|
|
70
|
-
if (!req.user) {
|
|
71
|
-
return next(new AppError('Authentication required', 401));
|
|
72
|
-
}
|
|
73
|
-
if (req.user.role !== role) {
|
|
74
|
-
return next(new AppError('Insufficient permissions', 403));
|
|
75
|
-
}
|
|
76
|
-
next();
|
|
77
|
-
};
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
module.exports = { authenticateToken, optionalAuth, requireRole };
|
|
68
|
+
module.exports = { authenticateToken, optionalAuth };
|
|
@@ -5,7 +5,7 @@ const router = express.Router();
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @swagger
|
|
8
|
-
*
|
|
8
|
+
* <% if (hasApiVersioning) { %>/v1<% } %>/ai/chat:
|
|
9
9
|
* post:
|
|
10
10
|
* summary: Send a chat message to the AI
|
|
11
11
|
* tags: [AI]
|
|
@@ -33,9 +33,6 @@ const router = express.Router();
|
|
|
33
33
|
* enum: [user, assistant]
|
|
34
34
|
* content:
|
|
35
35
|
* type: string
|
|
36
|
-
* provider:
|
|
37
|
-
* type: string
|
|
38
|
-
* enum: [openai, anthropic, gemini, ollama]
|
|
39
36
|
* model:
|
|
40
37
|
* type: string
|
|
41
38
|
* responses:
|
|
@@ -46,7 +43,7 @@ router.post('/chat', chatHandler);
|
|
|
46
43
|
|
|
47
44
|
/**
|
|
48
45
|
* @swagger
|
|
49
|
-
*
|
|
46
|
+
* <% if (hasApiVersioning) { %>/v1<% } %>/ai/stream:
|
|
50
47
|
* post:
|
|
51
48
|
* summary: Stream an AI response via Server-Sent Events
|
|
52
49
|
* tags: [AI]
|
|
@@ -68,7 +65,7 @@ router.post('/stream', streamHandler);
|
|
|
68
65
|
|
|
69
66
|
/**
|
|
70
67
|
* @swagger
|
|
71
|
-
*
|
|
68
|
+
* <% if (hasApiVersioning) { %>/v1<% } %>/ai/chain:
|
|
72
69
|
* post:
|
|
73
70
|
* summary: Run a prompt template chain with variable substitution
|
|
74
71
|
* tags: [AI]
|
|
@@ -86,9 +83,6 @@ router.post('/stream', streamHandler);
|
|
|
86
83
|
* variables:
|
|
87
84
|
* type: object
|
|
88
85
|
* example: { "text": "Node.js is a JavaScript runtime..." }
|
|
89
|
-
* provider:
|
|
90
|
-
* type: string
|
|
91
|
-
* enum: [openai, anthropic, gemini, ollama]
|
|
92
86
|
* responses:
|
|
93
87
|
* 200:
|
|
94
88
|
* description: Chain result
|
|
@@ -97,7 +91,7 @@ router.post('/chain', chainHandler);
|
|
|
97
91
|
|
|
98
92
|
/**
|
|
99
93
|
* @swagger
|
|
100
|
-
*
|
|
94
|
+
* <% if (hasApiVersioning) { %>/v1<% } %>/ai/agent:
|
|
101
95
|
* post:
|
|
102
96
|
* summary: Run the LangGraph ReAct agent with tool calling
|
|
103
97
|
* tags: [AI]
|
|
@@ -114,9 +108,6 @@ router.post('/chain', chainHandler);
|
|
|
114
108
|
* example: "What time is it right now?"
|
|
115
109
|
* systemPrompt:
|
|
116
110
|
* type: string
|
|
117
|
-
* provider:
|
|
118
|
-
* type: string
|
|
119
|
-
* enum: [openai, anthropic, gemini, ollama]
|
|
120
111
|
* responses:
|
|
121
112
|
* 200:
|
|
122
113
|
* description: Agent response with tool-use steps
|
|
@@ -2,7 +2,7 @@ const { getModel } = require('../config/ai');
|
|
|
2
2
|
const { HumanMessage, SystemMessage, AIMessage } = require('@langchain/core/messages');
|
|
3
3
|
const { StringOutputParser } = require('@langchain/core/output_parsers');
|
|
4
4
|
const { ChatPromptTemplate } = require('@langchain/core/prompts');
|
|
5
|
-
const logger = require('../utils/logger');
|
|
5
|
+
const { logger } = require('../utils/logger');
|
|
6
6
|
|
|
7
7
|
const DEFAULT_SYSTEM_PROMPT = 'You are a helpful AI assistant.';
|
|
8
8
|
|
|
@@ -17,9 +17,9 @@ describe('Example Endpoints', () => {
|
|
|
17
17
|
.get('/api/examples?page=1&limit=1');
|
|
18
18
|
|
|
19
19
|
expect(res.statusCode).toBe(200);
|
|
20
|
-
expect(res.body.meta).toHaveProperty('page', 1);
|
|
21
|
-
expect(res.body.meta).toHaveProperty('limit', 1);
|
|
22
|
-
expect(res.body.meta).toHaveProperty('total');
|
|
20
|
+
expect(res.body.meta.pagination).toHaveProperty('page', 1);
|
|
21
|
+
expect(res.body.meta.pagination).toHaveProperty('limit', 1);
|
|
22
|
+
expect(res.body.meta.pagination).toHaveProperty('total');
|
|
23
23
|
});
|
|
24
24
|
});
|
|
25
25
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// templates/tests/setup.js.ejs
|
|
2
2
|
// Global test setup
|
|
3
3
|
process.env.NODE_ENV = 'test';
|
|
4
|
-
<% if (
|
|
4
|
+
<% if (hasAuth) { %>process.env.JWT_SECRET = 'test-secret';
|
|
5
5
|
process.env.JWT_REFRESH_SECRET = 'test-refresh-secret';<% } %>
|
|
6
6
|
|
|
7
7
|
// Suppress console logs during tests
|