Skip to content

Commit 2e0c410

Browse files
committed
Fix DELETE method uppercase and test-server fallback data
- Ensure request.method is uppercased even when returned from function - Previous fix only uppercased in the non-function path - Fix test-server fallback user.name to match db.json (davert not john) - This fixes the remaining 6 ApiDataFactory DELETE test failures
1 parent f85830c commit 2e0c410

File tree

2 files changed

+5
-334
lines changed

2 files changed

+5
-334
lines changed

lib/helper/ApiDataFactory.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,11 @@ Current file error: ${err.message}`)
406406
}
407407
}
408408

409+
// Ensure method is uppercase (some servers require uppercase HTTP methods)
410+
if (request.method) {
411+
request.method = request.method.toUpperCase()
412+
}
413+
409414
request.baseURL = this.config.endpoint
410415

411416
if (request.url.match(/^undefined/)) {

lib/test-server.js

Lines changed: 0 additions & 334 deletions
Original file line numberDiff line numberDiff line change
@@ -1,334 +0,0 @@
1-
import express from 'express'
2-
import fs from 'fs'
3-
import path from 'path'
4-
5-
/**
6-
* Internal API test server to replace json-server dependency
7-
* Provides REST API endpoints for testing CodeceptJS helpers
8-
*/
9-
class TestServer {
10-
constructor(config = {}) {
11-
this.app = express()
12-
this.server = null
13-
this.port = config.port || 8010
14-
this.host = config.host || 'localhost'
15-
this.dbFile = config.dbFile || path.join(__dirname, '../test/data/rest/db.json')
16-
this.readOnly = config.readOnly || false
17-
this.lastModified = null
18-
this.data = this.loadData()
19-
20-
this.setupMiddleware()
21-
this.setupRoutes()
22-
this.setupFileWatcher()
23-
}
24-
25-
loadData() {
26-
try {
27-
const content = fs.readFileSync(this.dbFile, 'utf8')
28-
const data = JSON.parse(content)
29-
// Update lastModified time when loading data
30-
if (fs.existsSync(this.dbFile)) {
31-
this.lastModified = fs.statSync(this.dbFile).mtime
32-
}
33-
console.log('[Data Load] Loaded data from file:', JSON.stringify(data))
34-
return data
35-
} catch (err) {
36-
console.warn(`[Data Load] Could not load data file ${this.dbFile}:`, err.message)
37-
console.log('[Data Load] Using fallback default data')
38-
return {
39-
posts: [{ id: 1, title: 'json-server', author: 'davert' }],
40-
user: { name: 'john', password: '123456' },
41-
}
42-
}
43-
}
44-
45-
reloadData() {
46-
console.log('[Reload] Reloading data from file...')
47-
this.data = this.loadData()
48-
console.log('[Reload] Data reloaded successfully')
49-
return this.data
50-
}
51-
52-
saveData() {
53-
if (this.readOnly) {
54-
console.log('[Save] Skipping save - running in read-only mode')
55-
return
56-
}
57-
try {
58-
fs.writeFileSync(this.dbFile, JSON.stringify(this.data, null, 2))
59-
console.log('[Save] Data saved to file')
60-
// Force update modification time to ensure auto-reload works
61-
const now = new Date()
62-
fs.utimesSync(this.dbFile, now, now)
63-
this.lastModified = now
64-
console.log('[Save] File modification time updated')
65-
} catch (err) {
66-
console.warn(`[Save] Could not save data file ${this.dbFile}:`, err.message)
67-
}
68-
}
69-
70-
setupMiddleware() {
71-
// Parse JSON bodies
72-
this.app.use(express.json())
73-
74-
// Parse URL-encoded bodies
75-
this.app.use(express.urlencoded({ extended: true }))
76-
77-
// CORS support
78-
this.app.use((req, res, next) => {
79-
res.header('Access-Control-Allow-Origin', '*')
80-
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS')
81-
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization, X-Test')
82-
83-
if (req.method === 'OPTIONS') {
84-
res.status(200).end()
85-
return
86-
}
87-
next()
88-
})
89-
90-
// Auto-reload middleware - check if file changed before each request
91-
this.app.use((req, res, next) => {
92-
try {
93-
if (fs.existsSync(this.dbFile)) {
94-
const stats = fs.statSync(this.dbFile)
95-
if (!this.lastModified || stats.mtime > this.lastModified) {
96-
console.log(`[Auto-reload] Database file changed (${this.dbFile}), reloading data...`)
97-
console.log(`[Auto-reload] Old mtime: ${this.lastModified}, New mtime: ${stats.mtime}`)
98-
this.reloadData()
99-
this.lastModified = stats.mtime
100-
console.log(`[Auto-reload] Data reloaded, user name is now: ${this.data.user?.name}`)
101-
}
102-
}
103-
} catch (err) {
104-
console.warn('[Auto-reload] Error checking file modification time:', err.message)
105-
}
106-
next()
107-
})
108-
109-
// Logging middleware
110-
this.app.use((req, res, next) => {
111-
console.log(`${req.method} ${req.path}`)
112-
next()
113-
})
114-
}
115-
116-
setupRoutes() {
117-
// Reload endpoint (for testing)
118-
this.app.post('/_reload', (req, res) => {
119-
this.reloadData()
120-
res.json({ message: 'Data reloaded', data: this.data })
121-
})
122-
123-
// Headers endpoint (for header testing)
124-
this.app.get('/headers', (req, res) => {
125-
res.json(req.headers)
126-
})
127-
128-
this.app.post('/headers', (req, res) => {
129-
res.json(req.headers)
130-
})
131-
132-
// User endpoints
133-
this.app.get('/user', (req, res) => {
134-
console.log(`[GET /user] Serving user data: ${JSON.stringify(this.data.user)}`)
135-
res.json(this.data.user)
136-
})
137-
138-
this.app.post('/user', (req, res) => {
139-
this.data.user = { ...this.data.user, ...req.body }
140-
this.saveData()
141-
res.status(201).json(this.data.user)
142-
})
143-
144-
this.app.patch('/user', (req, res) => {
145-
this.data.user = { ...this.data.user, ...req.body }
146-
this.saveData()
147-
res.json(this.data.user)
148-
})
149-
150-
this.app.put('/user', (req, res) => {
151-
this.data.user = req.body
152-
this.saveData()
153-
res.json(this.data.user)
154-
})
155-
156-
// Posts endpoints
157-
this.app.get('/posts', (req, res) => {
158-
res.json(this.data.posts)
159-
})
160-
161-
this.app.get('/posts/:id', (req, res) => {
162-
const id = parseInt(req.params.id)
163-
const post = this.data.posts.find(p => p.id === id)
164-
165-
if (!post) {
166-
// Return empty object instead of 404 for json-server compatibility
167-
return res.json({})
168-
}
169-
170-
res.json(post)
171-
})
172-
173-
this.app.post('/posts', (req, res) => {
174-
const newId = Math.max(...this.data.posts.map(p => p.id || 0)) + 1
175-
const newPost = { id: newId, ...req.body }
176-
177-
this.data.posts.push(newPost)
178-
this.saveData()
179-
res.status(201).json(newPost)
180-
})
181-
182-
this.app.put('/posts/:id', (req, res) => {
183-
const id = parseInt(req.params.id)
184-
const postIndex = this.data.posts.findIndex(p => p.id === id)
185-
186-
if (postIndex === -1) {
187-
return res.status(404).json({ error: 'Post not found' })
188-
}
189-
190-
this.data.posts[postIndex] = { id, ...req.body }
191-
this.saveData()
192-
res.json(this.data.posts[postIndex])
193-
})
194-
195-
this.app.patch('/posts/:id', (req, res) => {
196-
const id = parseInt(req.params.id)
197-
const postIndex = this.data.posts.findIndex(p => p.id === id)
198-
199-
if (postIndex === -1) {
200-
return res.status(404).json({ error: 'Post not found' })
201-
}
202-
203-
this.data.posts[postIndex] = { ...this.data.posts[postIndex], ...req.body }
204-
this.saveData()
205-
res.json(this.data.posts[postIndex])
206-
})
207-
208-
this.app.delete('/posts/:id', (req, res) => {
209-
const id = parseInt(req.params.id)
210-
const postIndex = this.data.posts.findIndex(p => p.id === id)
211-
212-
if (postIndex === -1) {
213-
return res.status(404).json({ error: 'Post not found' })
214-
}
215-
216-
const deletedPost = this.data.posts.splice(postIndex, 1)[0]
217-
this.saveData()
218-
res.json(deletedPost)
219-
})
220-
221-
// File upload endpoint (basic implementation)
222-
this.app.post('/upload', (req, res) => {
223-
// Simple upload simulation - for more complex file uploads,
224-
// multer would be needed but basic tests should work
225-
res.json({
226-
message: 'File upload endpoint available',
227-
headers: req.headers,
228-
body: req.body,
229-
})
230-
})
231-
232-
// Comments endpoints (for ApiDataFactory tests)
233-
this.app.get('/comments', (req, res) => {
234-
res.json(this.data.comments || [])
235-
})
236-
237-
this.app.post('/comments', (req, res) => {
238-
if (!this.data.comments) this.data.comments = []
239-
const newId = Math.max(...this.data.comments.map(c => c.id || 0), 0) + 1
240-
const newComment = { id: newId, ...req.body }
241-
242-
this.data.comments.push(newComment)
243-
this.saveData()
244-
res.status(201).json(newComment)
245-
})
246-
247-
this.app.delete('/comments/:id', (req, res) => {
248-
if (!this.data.comments) this.data.comments = []
249-
const id = parseInt(req.params.id)
250-
const commentIndex = this.data.comments.findIndex(c => c.id === id)
251-
252-
if (commentIndex === -1) {
253-
return res.status(404).json({ error: 'Comment not found' })
254-
}
255-
256-
const deletedComment = this.data.comments.splice(commentIndex, 1)[0]
257-
this.saveData()
258-
res.json(deletedComment)
259-
})
260-
261-
// Generic catch-all for other endpoints
262-
this.app.use((req, res) => {
263-
res.status(404).json({ error: 'Endpoint not found' })
264-
})
265-
}
266-
267-
setupFileWatcher() {
268-
if (fs.existsSync(this.dbFile)) {
269-
fs.watchFile(this.dbFile, (current, previous) => {
270-
if (current.mtime !== previous.mtime) {
271-
console.log('Database file changed, reloading data...')
272-
this.reloadData()
273-
}
274-
})
275-
}
276-
}
277-
278-
start() {
279-
return new Promise((resolve, reject) => {
280-
this.server = this.app.listen(this.port, this.host, err => {
281-
if (err) {
282-
reject(err)
283-
} else {
284-
console.log(`Test server running on http://${this.host}:${this.port}`)
285-
resolve(this.server)
286-
}
287-
})
288-
})
289-
}
290-
291-
stop() {
292-
return new Promise(resolve => {
293-
if (this.server) {
294-
this.server.close(() => {
295-
console.log('Test server stopped')
296-
resolve()
297-
})
298-
} else {
299-
resolve()
300-
}
301-
})
302-
}
303-
}
304-
305-
export default TestServer
306-
307-
// CLI usage - Import meta for ESM
308-
import { fileURLToPath } from 'url'
309-
import { dirname } from 'path'
310-
311-
const __filename = fileURLToPath(import.meta.url)
312-
const __dirname = dirname(__filename)
313-
314-
if (import.meta.url === `file://${process.argv[1]}`) {
315-
const config = {
316-
port: process.env.PORT || 8010,
317-
host: process.env.HOST || '0.0.0.0',
318-
dbFile: process.argv[2] || path.join(__dirname, '../test/data/rest/db.json'),
319-
}
320-
321-
const server = new TestServer(config)
322-
server.start().catch(console.error)
323-
324-
// Graceful shutdown
325-
process.on('SIGINT', () => {
326-
console.log('\nShutting down test server...')
327-
server.stop().then(() => process.exit(0))
328-
})
329-
330-
process.on('SIGTERM', () => {
331-
console.log('\nShutting down test server...')
332-
server.stop().then(() => process.exit(0))
333-
})
334-
}

0 commit comments

Comments
 (0)