1'use strict';
2
3const common = require('../common');
4
5// This test ensures that fs.write accepts "named parameters" object
6// and doesn't interpret objects as strings
7
8const assert = require('assert');
9const fs = require('fs');
10const path = require('path');
11const tmpdir = require('../common/tmpdir');
12const util = require('util');
13
14tmpdir.refresh();
15
16const destInvalid = path.resolve(tmpdir.path, 'rwopt_invalid');
17const buffer = Buffer.from('zyx');
18
19function testInvalidCb(fd, expectedCode, buffer, options, callback) {
20  assert.throws(
21    () => fs.write(fd, buffer, common.mustNotMutateObjectDeep(options), common.mustNotCall()),
22    { code: expectedCode }
23  );
24  callback(0);
25}
26
27function testValidCb(buffer, options, index, callback) {
28  options = common.mustNotMutateObjectDeep(options);
29  const length = options?.length;
30  const offset = options?.offset;
31  const dest = path.resolve(tmpdir.path, `rwopt_valid_${index}`);
32  fs.open(dest, 'w', common.mustSucceed((fd) => {
33    fs.write(fd, buffer, options, common.mustSucceed((bytesWritten, bufferWritten) => {
34      const writeBufCopy = Uint8Array.prototype.slice.call(bufferWritten);
35      fs.close(fd, common.mustSucceed(() => {
36        fs.open(dest, 'r', common.mustSucceed((fd) => {
37          fs.read(fd, buffer, options, common.mustSucceed((bytesRead, bufferRead) => {
38            const readBufCopy = Uint8Array.prototype.slice.call(bufferRead);
39
40            assert.ok(bytesWritten >= bytesRead);
41            if (length !== undefined && length !== null) {
42              assert.strictEqual(bytesWritten, length);
43              assert.strictEqual(bytesRead, length);
44            }
45            if (offset === undefined || offset === 0) {
46              assert.deepStrictEqual(writeBufCopy, readBufCopy);
47            }
48            assert.deepStrictEqual(bufferWritten, bufferRead);
49            fs.close(fd, common.mustSucceed(callback));
50          }));
51        }));
52      }));
53    }));
54  }));
55}
56
57// Promisify to reduce flakiness
58const testInvalid = util.promisify(testInvalidCb);
59const testValid = util.promisify(testValidCb);
60
61async function runTests(fd) {
62  // Test if first argument is not wrongly interpreted as ArrayBufferView|string
63  for (const badBuffer of [
64    undefined, null, true, 42, 42n, Symbol('42'), NaN, [], () => {},
65    Promise.resolve(new Uint8Array(1)),
66    common.mustNotCall(),
67    common.mustNotMutateObjectDeep({}),
68    {},
69    { buffer: 'amNotParam' },
70    { string: 'amNotParam' },
71    { buffer: new Uint8Array(1).buffer },
72    new Date(),
73    new String('notPrimitive'),
74    { [Symbol.toPrimitive]: (hint) => 'amObject' },
75
76    // TODO(LiviaMedeiros): add the following after DEP0162 EOL
77    // { toString() { return 'amObject'; } },
78  ]) {
79    await testInvalid(fd, 'ERR_INVALID_ARG_TYPE', badBuffer, {});
80  }
81
82  // First argument (buffer or string) is mandatory
83  await testInvalid(fd, 'ERR_INVALID_ARG_TYPE', undefined, undefined);
84
85  // Various invalid options
86  await testInvalid(fd, 'ERR_OUT_OF_RANGE', buffer, { length: 5 });
87  await testInvalid(fd, 'ERR_OUT_OF_RANGE', buffer, { offset: 5 });
88  await testInvalid(fd, 'ERR_OUT_OF_RANGE', buffer, { length: 1, offset: 3 });
89  await testInvalid(fd, 'ERR_OUT_OF_RANGE', buffer, { length: -1 });
90  await testInvalid(fd, 'ERR_OUT_OF_RANGE', buffer, { offset: -1 });
91  await testInvalid(fd, 'ERR_INVALID_ARG_TYPE', buffer, { offset: false });
92  await testInvalid(fd, 'ERR_INVALID_ARG_TYPE', buffer, { offset: true });
93  await testInvalid(fd, 'ERR_INVALID_ARG_TYPE', buffer, true);
94  await testInvalid(fd, 'ERR_INVALID_ARG_TYPE', buffer, '42');
95  await testInvalid(fd, 'ERR_INVALID_ARG_TYPE', buffer, Symbol('42'));
96
97  // Test compatibility with fs.read counterpart
98  for (const [ index, options ] of [
99    null,
100    {},
101    { length: 1 },
102    { position: 5 },
103    { length: 1, position: 5 },
104    { length: 1, position: -1, offset: 2 },
105    { length: null },
106    { position: null },
107    { offset: 1 },
108  ].entries()) {
109    await testValid(buffer, options, index);
110  }
111}
112
113fs.open(destInvalid, 'w+', common.mustSucceed(async (fd) => {
114  runTests(fd).then(common.mustCall(() => fs.close(fd, common.mustSucceed())));
115}));
116