Skip to content

Commit d2b79fc

Browse files
committed
šŸ› Make Conduit#serveFile() actually call the options.onError callback when an error occurs
1 parent 629ce39 commit d2b79fc

3 files changed

Lines changed: 83 additions & 1 deletion

File tree

ā€ŽCHANGELOG.mdā€Ž

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* Plugin load failures will not stop Alchemy from starting
77
* `die()` will now wait 1 synchronous second before exiting
88
* Fix SettingValue Ā» GroupValue conversion in settings system
9+
* Make `Conduit#serveFile()` actually call the `options.onError` callback when an error occurs
910

1011
## 1.4.0 (2026-01-21)
1112

ā€Žlib/class/conduit.jsā€Ž

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2153,7 +2153,7 @@ Conduit.setTypedMethod([Types.String, Types.Object.optional()], function serveFi
21532153
*
21542154
* @author Jelle De Loecker <jelle@elevenways.be>
21552155
* @since 0.2.0
2156-
* @version 1.4.0
2156+
* @version 1.4.1
21572157
*
21582158
* @param {Alchemy.Inode.File} file The file to serve
21592159
* @param {Object} options Options, including headers
@@ -2171,6 +2171,9 @@ Conduit.setTypedMethod([Types.Alchemy.Inode.File, Types.Object.optional()], asyn
21712171
} catch (err) {
21722172

21732173
if (err.code == 'ENOENT') {
2174+
if (options.onError) {
2175+
return options.onError(err);
2176+
}
21742177
return this.notFound(err);
21752178
}
21762179

ā€Žtest/26-conduit.jsā€Ž

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const assert = require('assert');
22
const libfs = require('fs');
3+
const libpath = require('path');
34

45
let post_pledge;
56

@@ -74,6 +75,83 @@ describe('Controller', function() {
7475
await testFormSubmission(post_pledge, 'addfiles');
7576
});
7677
});
78+
79+
describe('#serveFile', function() {
80+
81+
let serve_file_pledge;
82+
83+
before(() => {
84+
Router.add({
85+
name : 'ConduitTest#serveFileTest',
86+
paths : '/conduit/serve_file_test',
87+
methods : 'get',
88+
});
89+
90+
ConduitTestController.setAction(async function serveFileTest(conduit) {
91+
let use_on_error = conduit.param('use_on_error') === 'true';
92+
let file_path = conduit.param('file_path');
93+
94+
// Create an Inode.File instance
95+
let file = new Classes.Alchemy.Inode.File(file_path);
96+
97+
let options = {};
98+
99+
if (use_on_error) {
100+
options.onError = (err) => {
101+
// Custom error handling: return a JSON response with error info
102+
conduit.setHeader('content-type', 'application/json');
103+
conduit.end(JSON.stringify({
104+
custom_error: true,
105+
error_code: err.code,
106+
error_message: err.message
107+
}));
108+
};
109+
}
110+
111+
return conduit.serveFile(file, options);
112+
});
113+
});
114+
115+
it('should call onError callback when serving a non-existent File object', async function() {
116+
117+
let non_existent_path = libpath.resolve(__dirname, 'does_not_exist_' + Date.now() + '.txt');
118+
let url = global.getRouteUrl('ConduitTest#serveFileTest');
119+
url += '?use_on_error=true&file_path=' + encodeURIComponent(non_existent_path);
120+
121+
let { response, body } = await harness.fetch(url);
122+
123+
// The onError callback should have been called, returning a JSON response
124+
// instead of the default 404 notFound behavior
125+
assert.strictEqual(response.statusCode, 200, 'Should return 200 because onError handled it');
126+
127+
// Body may already be parsed as JSON by Blast.fetch
128+
let parsed = typeof body === 'string' ? JSON.parse(body) : body;
129+
assert.strictEqual(parsed.custom_error, true, 'Should have custom_error flag');
130+
assert.strictEqual(parsed.error_code, 'ENOENT', 'Should have ENOENT error code');
131+
});
132+
133+
it('should call notFound when onError is not provided for non-existent File', async function() {
134+
135+
let non_existent_path = libpath.resolve(__dirname, 'does_not_exist_' + Date.now() + '.txt');
136+
let url = global.getRouteUrl('ConduitTest#serveFileTest');
137+
url += '?use_on_error=false&file_path=' + encodeURIComponent(non_existent_path);
138+
139+
// Without onError, notFound should be called, returning 404
140+
// Blast.fetch throws on 4xx/5xx status codes, so we need to catch the error
141+
let threw = false;
142+
let errorNumber;
143+
144+
try {
145+
await harness.fetch(url);
146+
} catch (err) {
147+
threw = true;
148+
errorNumber = err.number;
149+
}
150+
151+
assert.strictEqual(threw, true, 'Should throw an error for 404');
152+
assert.strictEqual(errorNumber, 404, 'Should return 404 when file not found and no onError');
153+
});
154+
});
77155
});
78156

79157
async function testFormSubmission(post_pledge, enctype) {

0 commit comments

Comments
Ā (0)