testdouble / quibble

Makes it easy to replace require'd dependencies.
94 stars 25 forks source link

Windows: fakeload fails to match due to case differences in path #83

Open cwienands1 opened 1 year ago

cwienands1 commented 1 year ago

I discovered this issue in Quibble 0.6.14 on my Windows developer machine.

I wrote a test case that is supposed to fake an inner dependency, like this:

// logic.js
const lwa = require("./login-with-aws"); // a local js file

module.exports.login = async (request, reply) => {
    return await lwa.retrieveProfile(request.body.accessToken);
};

// test-logic.js
let lwa;
let logic;

describe("handler", () => {
    beforeEach(async () => {
        lwa = td.replace("../src/login-with-aws");
        logic = require("../src/logic");
    });

    it("should login", async () => {
        td.when(lwa.retrieveProfile(td.matchers.anything())).thenResolve({ user_id: "xyz" });
        const profile = await logic.login({ body: { accessToken: "1234" } });
        console.log(profile);
    });
});

When logic executes, it does not use a fake/stub but the real implementation. I tracked the problem down to the following...

The require("./login-with-aws") call from logic.js eventually reaches the fakeload() method in quibble.js. After the quibble.absolutify() call, the path looks like this: C:\\Home\\my-project\\src\\logic.js Note the upper-case drive and folder names.

Inside stubbingThatMatchesRequest(), the stubbedPath variable starts with a lower-case drive letter, though, like c:\\Home\\my-project\\src\\login-with-aws.js, and therefore the fake/stub lookup fails. A quick fix for this problem looks like this...

var stubbingThatMatchesRequest = function (request) {
  request = request.toLowerCase();
  return _.ooFind(quibbles, function (stubbing, stubbedPath) {
    if (request === stubbedPath.toLowerCase()) return true
    if (nodeResolve(request) === stubbedPath) return true
  }, quibbles)
}

...but 1) this is ugly and probably the quibbles keys should already be added in lower-case format, 2) my quick and dirty fix could result in issues on Linux, which has a case-sensitive file system, and 3) there are probably more platform (Windows) specific issues in quibble, as I came across some strange behavior of testdouble, too.

searls commented 1 year ago

It definitely seems like someone should set up a proper development environment in Windows to try to ensure quibble works properly.

I take this to mean you're using Windows command prompt and not the Windows Subsystem for Linux?

cwienands1 commented 1 year ago

Yes, Windows command prompt (cmd) and Windows runtime environment. When I get a chance I can clone the repo and try to run the entire test suite locally on my machine and report back, get an idea where things stand ;-) The package.json file has 7 test-related entries. Which ones would be relevant in this case? Any other specifics or prerequisites, like minimum Node version or such?

searls commented 1 year ago

npm run test:ci runs the whole suite. If any fail (and it sounds like they will), it'll tell you which. Thanks!

cwienands1 commented 1 year ago

Quick update... I cloned the repo and tried running via the package.json commands. That didn't work because they are shell commands. To get something working I tried to convert test:esm and test:no-loader-esm to Windows commands.

"test:esm:win": "set NODE_OPTIONS=--loader=quibble && teenytest ./test/esm-lib/*.test.{mjs,js}",
"test:esm:win": "teenytest ./test/esm-lib/*.test.{mjs,js}",
"test:no-loader-esm:win": "teenytest './test/esm-lib/*.no-loader-test.js' && teenytest './test/esm-lib/*.no-loader-test.mjs'",

I'm not sure if I did those conversions correctly and I guess I didn't get very far... or I may already have run into Windows-specific problems. Output from first command:

> quibble@0.6.15 test:esm:win
> set NODE_OPTIONS=--loader=quibble && teenytest ./test/esm-lib/*.test.{mjs,js}

(node:27704) ExperimentalWarning: Custom ESM Loaders is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
node:internal/errors:477
    ErrorCaptureStackTrace(err);
    ^

TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension "" for C:\Home\alexa-piggy-bank\quibble\node_modules\teenytest\bin\teenytest
    at new NodeError (node:internal/errors:387:5)
    at Object.getFileProtocolModuleFormat [as file:] (node:internal/modules/esm/get_format:76:11)
    at defaultGetFormat (node:internal/modules/esm/get_format:118:38)
    at defaultLoad (node:internal/modules/esm/load:81:20)
    at nextLoad (node:internal/modules/esm/loader:165:28)
    at load (file:///C:/Home/alexa-piggy-bank/quibble/lib/quibble.mjs:135:7)
    at nextLoad (node:internal/modules/esm/loader:165:28)
    at ESMLoader.load (node:internal/modules/esm/loader:608:26)
    at ESMLoader.moduleProvider (node:internal/modules/esm/loader:464:22)
    at new ModuleJob (node:internal/modules/esm/module_job:63:26) {
  code: 'ERR_UNKNOWN_FILE_EXTENSION'
}

Output from second command:

> quibble@0.6.15 test:esm:win
> teenytest ./test/esm-lib/*.test.{mjs,js}

Error [ERR_UNSUPPORTED_ESM_URL_SCHEME]: Only URLs with a scheme in: file, data are supported by the default ESM loader. On Windows, absolute paths must be valid file:// URLs. Received protocol 'c:'
    at new NodeError (node:internal/errors:387:5)
    at throwIfUnsupportedURLScheme (node:internal/modules/esm/resolve:1116:11)
    at defaultResolve (node:internal/modules/esm/resolve:1196:3)
    at nextResolve (node:internal/modules/esm/loader:165:28)
    at ESMLoader.resolve (node:internal/modules/esm/loader:844:30)
    at ESMLoader.getModuleJob (node:internal/modules/esm/loader:431:18)
    at ESMLoader.import (node:internal/modules/esm/loader:528:22)
    at importModuleDynamically (node:internal/modules/cjs/loader:1065:29)
    at importModuleDynamicallyWrapper (node:internal/vm/module:438:21)
    at importModuleDynamically (node:vm:389:46)

I've seen that one before while digging either into this quibble issue or one I noticed in in testdouble (don't remember exactly). But this is clearly a platform-specific issue, as Windows paths start with a drive letter with a colon, and some URL logic inside quibble interprets those absolute file paths as URLs with a protocol prefix. Haven't had time yet to investigate more.

And output from third command:

> quibble@0.6.15 test:no-loader-esm:win
> teenytest './test/esm-lib/*.no-loader-test.js' && teenytest './test/esm-lib/*.no-loader-test.mjs'

TAP version 13
1..0
# Test run passed!
#   Passed: 0
#   Failed: 0
#   Total:  0
TAP version 13
1..0
# Test run passed!
#   Passed: 0
#   Failed: 0
#   Total:  0

0 tests passed? Doesn't look right...

Anyway, if I have some more time, I'll put this into Docker Desktop to see what the expected output should be from the original package.json test:... commands. That may help constructing the appropriate test:...:win commands for Windows. And then finally dig into any failures. Let me know if you have any thoughts about what you see here in the meantime.

searls commented 1 year ago

It will definitely be a non-trivial amount of work to make sure quibble works correctly under the win32 command prompt. It would be good to have, but I'm not sure whether it'll be worth the time investment. I'm curious how many developers using Windows and writing Node apps are running them are using Windows Subsystem for Linux (WSL); in my experience over the last few years, it seemed like adoption was immediate and extremely high, given how broken so many tools are in Node.js under Windows

cwienands1 commented 1 year ago

In the past all my personal NodeJS development as well as that of the teams I worked with was always on Windows. Don't know why but I never had to question yet whether that was the best way to do it or not. Speaking of quibble specifically, I agree that it looks like a lot of work to make everything run under Windows. I may have some more time to look into this later. Right now my family is expecting me to complete some home automation project first ;-)

webstech commented 1 year ago

@cwienands1 There have been some recent patches for Windows support. You may want to try again with the latest version.