github.com/dop251/goja_nodejs@v0.0.0-20240418154818-2aae10d4cbcf/require/module_test.go (about)

     1  package require
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"path"
    10  	"path/filepath"
    11  	"runtime"
    12  	"testing"
    13  
    14  	js "github.com/dop251/goja"
    15  )
    16  
    17  func mapFileSystemSourceLoader(files map[string]string) SourceLoader {
    18  	return func(path string) ([]byte, error) {
    19  		s, ok := files[path]
    20  		if !ok {
    21  			return nil, ModuleFileDoesNotExistError
    22  		}
    23  		return []byte(s), nil
    24  	}
    25  }
    26  
    27  func TestRequireNativeModule(t *testing.T) {
    28  	const SCRIPT = `
    29  	var m = require("test/m");
    30  	m.test();
    31  	`
    32  
    33  	vm := js.New()
    34  
    35  	registry := new(Registry)
    36  	registry.Enable(vm)
    37  
    38  	RegisterNativeModule("test/m", func(runtime *js.Runtime, module *js.Object) {
    39  		o := module.Get("exports").(*js.Object)
    40  		o.Set("test", func(call js.FunctionCall) js.Value {
    41  			return runtime.ToValue("passed")
    42  		})
    43  	})
    44  
    45  	v, err := vm.RunString(SCRIPT)
    46  	if err != nil {
    47  		t.Fatal(err)
    48  	}
    49  
    50  	if !v.StrictEquals(vm.ToValue("passed")) {
    51  		t.Fatalf("Unexpected result: %v", v)
    52  	}
    53  }
    54  
    55  func TestRegisterCoreModule(t *testing.T) {
    56  	vm := js.New()
    57  
    58  	registry := new(Registry)
    59  	registry.Enable(vm)
    60  
    61  	RegisterCoreModule("coremod", func(runtime *js.Runtime, module *js.Object) {
    62  		o := module.Get("exports").(*js.Object)
    63  		o.Set("test", func(call js.FunctionCall) js.Value {
    64  			return runtime.ToValue("passed")
    65  		})
    66  	})
    67  
    68  	RegisterCoreModule("coremod1", func(runtime *js.Runtime, module *js.Object) {
    69  		o := module.Get("exports").(*js.Object)
    70  		o.Set("test", func(call js.FunctionCall) js.Value {
    71  			return runtime.ToValue("passed1")
    72  		})
    73  	})
    74  
    75  	RegisterCoreModule("node:test1", func(runtime *js.Runtime, module *js.Object) {
    76  		o := module.Get("exports").(*js.Object)
    77  		o.Set("test", func(call js.FunctionCall) js.Value {
    78  			return runtime.ToValue("test1 passed")
    79  		})
    80  	})
    81  
    82  	registry.RegisterNativeModule("bob", func(runtime *js.Runtime, module *js.Object) {
    83  
    84  	})
    85  
    86  	_, err := vm.RunString(`
    87  	const m1 = require("coremod");
    88  	const m2 = require("node:coremod");
    89  	if (m1 !== m2) {
    90  		throw new Error("Modules are not equal");
    91  	}
    92  	if (m1.test() !== "passed") {
    93  		throw new Error("m1.test() has failed");
    94  	}
    95  
    96  	const m3 = require("node:coremod1");
    97  	const m4 = require("coremod1");
    98  	if (m3 !== m4) {
    99  		throw new Error("Modules are not equal (1)");
   100  	}
   101  	if (m3.test() !== "passed1") {
   102  		throw new Error("m3.test() has failed");
   103  	}
   104  
   105  	try {
   106  		require("node:bob");
   107  	} catch (e) {
   108  		if (!e.message.includes("No such built-in module")) {
   109  			throw e;
   110  		}
   111  	}
   112  	require("bob");
   113  
   114  	try {
   115  		require("test1");
   116  		throw new Error("Expected exception");
   117  	} catch (e) {
   118  		if (!e.message.includes("Invalid module")) {
   119  			throw e;
   120  		}
   121  	}
   122  
   123  	if (require("node:test1").test() !== "test1 passed") {
   124  		throw new Error("test1.test() has failed");
   125  	}
   126  	`)
   127  
   128  	if err != nil {
   129  		t.Fatal(err)
   130  	}
   131  }
   132  
   133  func TestRequireRegistryNativeModule(t *testing.T) {
   134  	const SCRIPT = `
   135  	var log = require("test/log");
   136  	log.print('passed');
   137  	`
   138  
   139  	logWithOutput := func(w io.Writer, prefix string) ModuleLoader {
   140  		return func(vm *js.Runtime, module *js.Object) {
   141  			o := module.Get("exports").(*js.Object)
   142  			o.Set("print", func(call js.FunctionCall) js.Value {
   143  				fmt.Fprint(w, prefix, call.Argument(0).String())
   144  				return js.Undefined()
   145  			})
   146  		}
   147  	}
   148  
   149  	vm1 := js.New()
   150  	buf1 := &bytes.Buffer{}
   151  
   152  	registry1 := new(Registry)
   153  	registry1.Enable(vm1)
   154  
   155  	registry1.RegisterNativeModule("test/log", logWithOutput(buf1, "vm1 "))
   156  
   157  	vm2 := js.New()
   158  	buf2 := &bytes.Buffer{}
   159  
   160  	registry2 := new(Registry)
   161  	registry2.Enable(vm2)
   162  
   163  	registry2.RegisterNativeModule("test/log", logWithOutput(buf2, "vm2 "))
   164  
   165  	_, err := vm1.RunString(SCRIPT)
   166  	if err != nil {
   167  		t.Fatal(err)
   168  	}
   169  
   170  	s := buf1.String()
   171  	if s != "vm1 passed" {
   172  		t.Fatalf("vm1: Unexpected result: %q", s)
   173  	}
   174  
   175  	_, err = vm2.RunString(SCRIPT)
   176  	if err != nil {
   177  		t.Fatal(err)
   178  	}
   179  
   180  	s = buf2.String()
   181  	if s != "vm2 passed" {
   182  		t.Fatalf("vm2: Unexpected result: %q", s)
   183  	}
   184  }
   185  
   186  func TestRequire(t *testing.T) {
   187  	absPath, err := filepath.Abs("./testdata/m.js")
   188  	if err != nil {
   189  		t.Fatal(err)
   190  	}
   191  
   192  	isWindows := runtime.GOOS == "windows"
   193  
   194  	tests := []struct {
   195  		path string
   196  		ok   bool
   197  	}{
   198  		{
   199  			"./testdata/m.js",
   200  			true,
   201  		},
   202  		{
   203  			"../require/testdata/m.js",
   204  			true,
   205  		},
   206  		{
   207  			absPath,
   208  			true,
   209  		},
   210  		{
   211  			`.\testdata\m.js`,
   212  			isWindows,
   213  		},
   214  		{
   215  			`..\require\testdata\m.js`,
   216  			isWindows,
   217  		},
   218  	}
   219  
   220  	const SCRIPT = `
   221  	var m = require(testPath);
   222  	m.test();
   223  	`
   224  
   225  	for _, test := range tests {
   226  		t.Run(test.path, func(t *testing.T) {
   227  			vm := js.New()
   228  			vm.Set("testPath", test.path)
   229  
   230  			registry := new(Registry)
   231  			registry.Enable(vm)
   232  
   233  			v, err := vm.RunString(SCRIPT)
   234  
   235  			ok := err == nil
   236  
   237  			if ok != test.ok {
   238  				t.Fatalf("Expected ok to be %v, got %v (%v)", test.ok, ok, err)
   239  			}
   240  
   241  			if !ok {
   242  				return
   243  			}
   244  
   245  			if !v.StrictEquals(vm.ToValue("passed")) {
   246  				t.Fatalf("Unexpected result: %v", v)
   247  			}
   248  		})
   249  	}
   250  }
   251  
   252  func TestSourceLoader(t *testing.T) {
   253  	const SCRIPT = `
   254  	var m = require("m.js");
   255  	m.test();
   256  	`
   257  
   258  	const MODULE = `
   259  	function test() {
   260  		return "passed1";
   261  	}
   262  
   263  	exports.test = test;
   264  	`
   265  
   266  	vm := js.New()
   267  
   268  	registry := NewRegistry(WithGlobalFolders("."), WithLoader(func(name string) ([]byte, error) {
   269  		if name == "m.js" {
   270  			return []byte(MODULE), nil
   271  		}
   272  		return nil, errors.New("Module does not exist")
   273  	}))
   274  	registry.Enable(vm)
   275  
   276  	v, err := vm.RunString(SCRIPT)
   277  	if err != nil {
   278  		t.Fatal(err)
   279  	}
   280  
   281  	if !v.StrictEquals(vm.ToValue("passed1")) {
   282  		t.Fatalf("Unexpected result: %v", v)
   283  	}
   284  }
   285  
   286  func TestStrictModule(t *testing.T) {
   287  	const SCRIPT = `
   288  	var m = require("m.js");
   289  	m.test();
   290  	`
   291  
   292  	const MODULE = `
   293  	"use strict";
   294  
   295  	function test() {
   296  		var a = "passed1";
   297  		eval("var a = 'not passed'");
   298  		return a;
   299  	}
   300  
   301  	exports.test = test;
   302  	`
   303  
   304  	vm := js.New()
   305  
   306  	registry := NewRegistry(WithGlobalFolders("."), WithLoader(func(name string) ([]byte, error) {
   307  		if name == "m.js" {
   308  			return []byte(MODULE), nil
   309  		}
   310  		return nil, errors.New("Module does not exist")
   311  	}))
   312  	registry.Enable(vm)
   313  
   314  	v, err := vm.RunString(SCRIPT)
   315  	if err != nil {
   316  		t.Fatal(err)
   317  	}
   318  
   319  	if !v.StrictEquals(vm.ToValue("passed1")) {
   320  		t.Fatalf("Unexpected result: %v", v)
   321  	}
   322  }
   323  
   324  func TestResolve(t *testing.T) {
   325  	testRequire := func(src, fpath string, globalFolders []string, fs map[string]string) (*js.Runtime, js.Value, error) {
   326  		vm := js.New()
   327  		r := NewRegistry(WithGlobalFolders(globalFolders...), WithLoader(mapFileSystemSourceLoader(fs)))
   328  		r.Enable(vm)
   329  		t.Logf("Require(%s)", fpath)
   330  		ret, err := vm.RunScript(path.Join(src, "test.js"), fmt.Sprintf("require('%s')", fpath))
   331  		if err != nil {
   332  			return nil, nil, err
   333  		}
   334  		return vm, ret, nil
   335  	}
   336  
   337  	globalFolders := []string{
   338  		"/usr/lib/node_modules",
   339  		"/home/src/.node_modules",
   340  	}
   341  
   342  	fs := map[string]string{
   343  		"/home/src/app/app.js":                   `exports.name = "app"`,
   344  		"/home/src/app2/app2.json":               `{"name": "app2"}`,
   345  		"/home/src/app3/index.js":                `exports.name = "app3"`,
   346  		"/home/src/app4/index.json":              `{"name": "app4"}`,
   347  		"/home/src/app5/package.json":            `{"main": "app5.js"}`,
   348  		"/home/src/app5/app5.js":                 `exports.name = "app5"`,
   349  		"/home/src/app6/package.json":            `{"main": "."}`,
   350  		"/home/src/app6/index.js":                `exports.name = "app6"`,
   351  		"/home/src/app7/package.json":            `{"main": "./a/b/c/file.js"}`,
   352  		"/home/src/app7/a/b/c/file.js":           `exports.name = "app7"`,
   353  		"/usr/lib/node_modules/app8":             `exports.name = "app8"`,
   354  		"/home/src/app9/app9.js":                 `exports.name = require('./a/file.js').name`,
   355  		"/home/src/app9/a/file.js":               `exports.name = require('./b/file.js').name`,
   356  		"/home/src/app9/a/b/file.js":             `exports.name = require('./c/file.js').name`,
   357  		"/home/src/app9/a/b/c/file.js":           `exports.name = "app9"`,
   358  		"/home/src/.node_modules/app10":          `exports.name = "app10"`,
   359  		"/home/src/app11/app11.js":               `exports.name = require('d/file.js').name`,
   360  		"/home/src/app11/a/b/c/app11.js":         `exports.name = require('d/file.js').name`,
   361  		"/home/src/app11/node_modules/d/file.js": `exports.name = "app11"`,
   362  		"/app12.js":                              `exports.name = require('a/file.js').name`,
   363  		"/node_modules/a/file.js":                `exports.name = "app12"`,
   364  		"/app13/app13.js":                        `exports.name = require('b/file.js').name`,
   365  		"/node_modules/b/file.js":                `exports.name = "app13"`,
   366  		"node_modules/app14/index.js":            `exports.name = "app14"`,
   367  		"../node_modules/app15/index.js":         `exports.name = "app15"`,
   368  	}
   369  
   370  	for i, tc := range []struct {
   371  		src   string
   372  		path  string
   373  		ok    bool
   374  		field string
   375  		value string
   376  	}{
   377  		{"/home/src", "./app/app", true, "name", "app"},
   378  		{"/home/src", "./app/app.js", true, "name", "app"},
   379  		{"/home/src", "./app/bad.js", false, "", ""},
   380  		{"/home/src", "./app2/app2", true, "name", "app2"},
   381  		{"/home/src", "./app2/app2.json", true, "name", "app2"},
   382  		{"/home/src", "./app/bad.json", false, "", ""},
   383  		{"/home/src", "./app3", true, "name", "app3"},
   384  		{"/home/src", "./appbad", false, "", ""},
   385  		{"/home/src", "./app4", true, "name", "app4"},
   386  		{"/home/src", "./appbad", false, "", ""},
   387  		{"/home/src", "./app5", true, "name", "app5"},
   388  		{"/home/src", "./app6", true, "name", "app6"},
   389  		{"/home/src", "./app7", true, "name", "app7"},
   390  		{"/home/src", "app8", true, "name", "app8"},
   391  		{"/home/src", "./app9/app9", true, "name", "app9"},
   392  		{"/home/src", "app10", true, "name", "app10"},
   393  		{"/home/src", "./app11/app11.js", true, "name", "app11"},
   394  		{"/home/src", "./app11/a/b/c/app11.js", true, "name", "app11"},
   395  		{"/", "./app12", true, "name", "app12"},
   396  		{"/", "./app13/app13", true, "name", "app13"},
   397  		{".", "app14", true, "name", "app14"},
   398  		{"..", "nonexistent", false, "", ""},
   399  	} {
   400  		vm, mod, err := testRequire(tc.src, tc.path, globalFolders, fs)
   401  		if err != nil {
   402  			if tc.ok {
   403  				t.Errorf("%d: require() failed: %v", i, err)
   404  			}
   405  			continue
   406  		} else {
   407  			if !tc.ok {
   408  				t.Errorf("%d: expected to fail, but did not", i)
   409  				continue
   410  			}
   411  		}
   412  		f := mod.ToObject(vm).Get(tc.field)
   413  		if f == nil {
   414  			t.Errorf("%v: field %q not found", i, tc.field)
   415  			continue
   416  		}
   417  		value := f.String()
   418  		if value != tc.value {
   419  			t.Errorf("%v: got %q expected %q", i, value, tc.value)
   420  		}
   421  	}
   422  }
   423  
   424  func TestRequireCycle(t *testing.T) {
   425  	vm := js.New()
   426  	r := NewRegistry(WithLoader(mapFileSystemSourceLoader(map[string]string{
   427  		"a.js": `var b = require('./b.js'); exports.done = true;`,
   428  		"b.js": `var a = require('./a.js'); exports.done = true;`,
   429  	})))
   430  	r.Enable(vm)
   431  	res, err := vm.RunString(`
   432  	var a = require('./a.js');
   433  	var b = require('./b.js');
   434  	a.done && b.done;
   435  	`)
   436  	if err != nil {
   437  		t.Fatal(err)
   438  	}
   439  	if v := res.Export(); v != true {
   440  		t.Fatalf("Unexpected result: %v", v)
   441  	}
   442  }
   443  
   444  func TestErrorPropagation(t *testing.T) {
   445  	vm := js.New()
   446  	r := NewRegistry(WithLoader(mapFileSystemSourceLoader(map[string]string{
   447  		"m.js": `throw 'test passed';`,
   448  	})))
   449  	rr := r.Enable(vm)
   450  	_, err := rr.Require("./m")
   451  	if err == nil {
   452  		t.Fatal("Expected an error")
   453  	}
   454  	if ex, ok := err.(*js.Exception); ok {
   455  		if !ex.Value().StrictEquals(vm.ToValue("test passed")) {
   456  			t.Fatalf("Unexpected Exception: %v", ex)
   457  		}
   458  	} else {
   459  		t.Fatal(err)
   460  	}
   461  }
   462  
   463  func TestSourceMapLoader(t *testing.T) {
   464  	vm := js.New()
   465  	r := NewRegistry(WithLoader(func(p string) ([]byte, error) {
   466  		switch p {
   467  		case "dir/m.js":
   468  			return []byte(`throw 'test passed';
   469  //# sourceMappingURL=m.js.map`), nil
   470  		case "dir/m.js.map":
   471  			return []byte(`{"version":3,"file":"m.js","sourceRoot":"","sources":["m.ts"],"names":[],"mappings":";AAAA"}
   472  `), nil
   473  		}
   474  		return nil, ModuleFileDoesNotExistError
   475  	}))
   476  
   477  	rr := r.Enable(vm)
   478  	_, err := rr.Require("./dir/m")
   479  	if err == nil {
   480  		t.Fatal("Expected an error")
   481  	}
   482  	if ex, ok := err.(*js.Exception); ok {
   483  		if !ex.Value().StrictEquals(vm.ToValue("test passed")) {
   484  			t.Fatalf("Unexpected Exception: %v", ex)
   485  		}
   486  	} else {
   487  		t.Fatal(err)
   488  	}
   489  }
   490  
   491  func testsetup() (string, func(), error) {
   492  	name, err := os.MkdirTemp("", "goja-nodejs-require-test")
   493  	if err != nil {
   494  		return "", nil, err
   495  	}
   496  	return name, func() {
   497  		os.RemoveAll(name)
   498  	}, nil
   499  }
   500  
   501  func TestDefaultModuleLoader(t *testing.T) {
   502  	workdir, teardown, err := testsetup()
   503  	if err != nil {
   504  		t.Fatal(err)
   505  	}
   506  	defer teardown()
   507  
   508  	err = os.Chdir(workdir)
   509  	if err != nil {
   510  		t.Fatal(err)
   511  	}
   512  	err = os.Mkdir("module", 0755)
   513  	if err != nil {
   514  		t.Fatal(err)
   515  	}
   516  	err = os.WriteFile("module/index.js", []byte(`throw 'test passed';`), 0644)
   517  	if err != nil {
   518  		t.Fatal(err)
   519  	}
   520  	vm := js.New()
   521  	r := NewRegistry()
   522  	rr := r.Enable(vm)
   523  	_, err = rr.Require("./module")
   524  	if err == nil {
   525  		t.Fatal("Expected an error")
   526  	}
   527  	if ex, ok := err.(*js.Exception); ok {
   528  		if !ex.Value().StrictEquals(vm.ToValue("test passed")) {
   529  			t.Fatalf("Unexpected Exception: %v", ex)
   530  		}
   531  	} else {
   532  		t.Fatal(err)
   533  	}
   534  }