github.com/mariotoffia/goja_nodejs@v0.0.0-20221208130254-70b39c043ea9/require/module_test.go (about)

     1  package require
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"path"
    10  	"testing"
    11  
    12  	js "github.com/dop251/goja"
    13  )
    14  
    15  func mapFileSystemSourceLoader(files map[string]string) SourceLoader {
    16  	return func(path string) ([]byte, error) {
    17  		s, ok := files[path]
    18  		if !ok {
    19  			return nil, ModuleFileDoesNotExistError
    20  		}
    21  		return []byte(s), nil
    22  	}
    23  }
    24  
    25  func TestRequireNativeModule(t *testing.T) {
    26  	const SCRIPT = `
    27  	var m = require("test/m");
    28  	m.test();
    29  	`
    30  
    31  	vm := js.New()
    32  
    33  	registry := new(Registry)
    34  	registry.Enable(vm)
    35  
    36  	RegisterNativeModule("test/m", func(runtime *js.Runtime, module *js.Object) {
    37  		o := module.Get("exports").(*js.Object)
    38  		o.Set("test", func(call js.FunctionCall) js.Value {
    39  			return runtime.ToValue("passed")
    40  		})
    41  	})
    42  
    43  	v, err := vm.RunString(SCRIPT)
    44  	if err != nil {
    45  		t.Fatal(err)
    46  	}
    47  
    48  	if !v.StrictEquals(vm.ToValue("passed")) {
    49  		t.Fatalf("Unexpected result: %v", v)
    50  	}
    51  }
    52  
    53  func TestRequireRegistryNativeModule(t *testing.T) {
    54  	const SCRIPT = `
    55  	var log = require("test/log");
    56  	log.print('passed');
    57  	`
    58  
    59  	logWithOutput := func(w io.Writer, prefix string) ModuleLoader {
    60  		return func(vm *js.Runtime, module *js.Object) {
    61  			o := module.Get("exports").(*js.Object)
    62  			o.Set("print", func(call js.FunctionCall) js.Value {
    63  				fmt.Fprint(w, prefix, call.Argument(0).String())
    64  				return js.Undefined()
    65  			})
    66  		}
    67  	}
    68  
    69  	vm1 := js.New()
    70  	buf1 := &bytes.Buffer{}
    71  
    72  	registry1 := new(Registry)
    73  	registry1.Enable(vm1)
    74  
    75  	registry1.RegisterNativeModule("test/log", logWithOutput(buf1, "vm1 "))
    76  
    77  	vm2 := js.New()
    78  	buf2 := &bytes.Buffer{}
    79  
    80  	registry2 := new(Registry)
    81  	registry2.Enable(vm2)
    82  
    83  	registry2.RegisterNativeModule("test/log", logWithOutput(buf2, "vm2 "))
    84  
    85  	_, err := vm1.RunString(SCRIPT)
    86  	if err != nil {
    87  		t.Fatal(err)
    88  	}
    89  
    90  	s := buf1.String()
    91  	if s != "vm1 passed" {
    92  		t.Fatalf("vm1: Unexpected result: %q", s)
    93  	}
    94  
    95  	_, err = vm2.RunString(SCRIPT)
    96  	if err != nil {
    97  		t.Fatal(err)
    98  	}
    99  
   100  	s = buf2.String()
   101  	if s != "vm2 passed" {
   102  		t.Fatalf("vm2: Unexpected result: %q", s)
   103  	}
   104  }
   105  
   106  func TestRequire(t *testing.T) {
   107  	const SCRIPT = `
   108  	var m = require("./testdata/m.js");
   109  	m.test();
   110  	`
   111  
   112  	vm := js.New()
   113  
   114  	registry := new(Registry)
   115  	registry.Enable(vm)
   116  
   117  	v, err := vm.RunString(SCRIPT)
   118  	if err != nil {
   119  		t.Fatal(err)
   120  	}
   121  
   122  	if !v.StrictEquals(vm.ToValue("passed")) {
   123  		t.Fatalf("Unexpected result: %v", v)
   124  	}
   125  }
   126  
   127  func TestSourceLoader(t *testing.T) {
   128  	const SCRIPT = `
   129  	var m = require("m.js");
   130  	m.test();
   131  	`
   132  
   133  	const MODULE = `
   134  	function test() {
   135  		return "passed1";
   136  	}
   137  
   138  	exports.test = test;
   139  	`
   140  
   141  	vm := js.New()
   142  
   143  	registry := NewRegistry(WithGlobalFolders("."), WithLoader(func(name string) ([]byte, error) {
   144  		if name == "m.js" {
   145  			return []byte(MODULE), nil
   146  		}
   147  		return nil, errors.New("Module does not exist")
   148  	}))
   149  	registry.Enable(vm)
   150  
   151  	v, err := vm.RunString(SCRIPT)
   152  	if err != nil {
   153  		t.Fatal(err)
   154  	}
   155  
   156  	if !v.StrictEquals(vm.ToValue("passed1")) {
   157  		t.Fatalf("Unexpected result: %v", v)
   158  	}
   159  }
   160  
   161  func TestStrictModule(t *testing.T) {
   162  	const SCRIPT = `
   163  	var m = require("m.js");
   164  	m.test();
   165  	`
   166  
   167  	const MODULE = `
   168  	"use strict";
   169  
   170  	function test() {
   171  		var a = "passed1";
   172  		eval("var a = 'not passed'");
   173  		return a;
   174  	}
   175  
   176  	exports.test = test;
   177  	`
   178  
   179  	vm := js.New()
   180  
   181  	registry := NewRegistry(WithGlobalFolders("."), WithLoader(func(name string) ([]byte, error) {
   182  		if name == "m.js" {
   183  			return []byte(MODULE), nil
   184  		}
   185  		return nil, errors.New("Module does not exist")
   186  	}))
   187  	registry.Enable(vm)
   188  
   189  	v, err := vm.RunString(SCRIPT)
   190  	if err != nil {
   191  		t.Fatal(err)
   192  	}
   193  
   194  	if !v.StrictEquals(vm.ToValue("passed1")) {
   195  		t.Fatalf("Unexpected result: %v", v)
   196  	}
   197  }
   198  
   199  func TestResolve(t *testing.T) {
   200  	testRequire := func(src, fpath string, globalFolders []string, fs map[string]string) (*js.Runtime, js.Value, error) {
   201  		vm := js.New()
   202  		r := NewRegistry(WithGlobalFolders(globalFolders...), WithLoader(mapFileSystemSourceLoader(fs)))
   203  		r.Enable(vm)
   204  		t.Logf("Require(%s)", fpath)
   205  		ret, err := vm.RunScript(path.Join(src, "test.js"), fmt.Sprintf("require('%s')", fpath))
   206  		if err != nil {
   207  			return nil, nil, err
   208  		}
   209  		return vm, ret, nil
   210  	}
   211  
   212  	globalFolders := []string{
   213  		"/usr/lib/node_modules",
   214  		"/home/src/.node_modules",
   215  	}
   216  
   217  	fs := map[string]string{
   218  		"/home/src/app/app.js":                   `exports.name = "app"`,
   219  		"/home/src/app2/app2.json":               `{"name": "app2"}`,
   220  		"/home/src/app3/index.js":                `exports.name = "app3"`,
   221  		"/home/src/app4/index.json":              `{"name": "app4"}`,
   222  		"/home/src/app5/package.json":            `{"main": "app5.js"}`,
   223  		"/home/src/app5/app5.js":                 `exports.name = "app5"`,
   224  		"/home/src/app6/package.json":            `{"main": "."}`,
   225  		"/home/src/app6/index.js":                `exports.name = "app6"`,
   226  		"/home/src/app7/package.json":            `{"main": "./a/b/c/file.js"}`,
   227  		"/home/src/app7/a/b/c/file.js":           `exports.name = "app7"`,
   228  		"/usr/lib/node_modules/app8":             `exports.name = "app8"`,
   229  		"/home/src/app9/app9.js":                 `exports.name = require('./a/file.js').name`,
   230  		"/home/src/app9/a/file.js":               `exports.name = require('./b/file.js').name`,
   231  		"/home/src/app9/a/b/file.js":             `exports.name = require('./c/file.js').name`,
   232  		"/home/src/app9/a/b/c/file.js":           `exports.name = "app9"`,
   233  		"/home/src/.node_modules/app10":          `exports.name = "app10"`,
   234  		"/home/src/app11/app11.js":               `exports.name = require('d/file.js').name`,
   235  		"/home/src/app11/a/b/c/app11.js":         `exports.name = require('d/file.js').name`,
   236  		"/home/src/app11/node_modules/d/file.js": `exports.name = "app11"`,
   237  		"/app12.js":                              `exports.name = require('a/file.js').name`,
   238  		"/node_modules/a/file.js":                `exports.name = "app12"`,
   239  		"/app13/app13.js":                        `exports.name = require('b/file.js').name`,
   240  		"/node_modules/b/file.js":                `exports.name = "app13"`,
   241  		"node_modules/app14/index.js":            `exports.name = "app14"`,
   242  		"../node_modules/app15/index.js":         `exports.name = "app15"`,
   243  	}
   244  
   245  	for i, tc := range []struct {
   246  		src   string
   247  		path  string
   248  		ok    bool
   249  		field string
   250  		value string
   251  	}{
   252  		{"/home/src", "./app/app", true, "name", "app"},
   253  		{"/home/src", "./app/app.js", true, "name", "app"},
   254  		{"/home/src", "./app/bad.js", false, "", ""},
   255  		{"/home/src", "./app2/app2", true, "name", "app2"},
   256  		{"/home/src", "./app2/app2.json", true, "name", "app2"},
   257  		{"/home/src", "./app/bad.json", false, "", ""},
   258  		{"/home/src", "./app3", true, "name", "app3"},
   259  		{"/home/src", "./appbad", false, "", ""},
   260  		{"/home/src", "./app4", true, "name", "app4"},
   261  		{"/home/src", "./appbad", false, "", ""},
   262  		{"/home/src", "./app5", true, "name", "app5"},
   263  		{"/home/src", "./app6", true, "name", "app6"},
   264  		{"/home/src", "./app7", true, "name", "app7"},
   265  		{"/home/src", "app8", true, "name", "app8"},
   266  		{"/home/src", "./app9/app9", true, "name", "app9"},
   267  		{"/home/src", "app10", true, "name", "app10"},
   268  		{"/home/src", "./app11/app11.js", true, "name", "app11"},
   269  		{"/home/src", "./app11/a/b/c/app11.js", true, "name", "app11"},
   270  		{"/", "./app12", true, "name", "app12"},
   271  		{"/", "./app13/app13", true, "name", "app13"},
   272  		{".", "app14", true, "name", "app14"},
   273  		{"..", "nonexistent", false, "", ""},
   274  	} {
   275  		vm, mod, err := testRequire(tc.src, tc.path, globalFolders, fs)
   276  		if err != nil {
   277  			if tc.ok {
   278  				t.Errorf("%d: require() failed: %v", i, err)
   279  			}
   280  			continue
   281  		} else {
   282  			if !tc.ok {
   283  				t.Errorf("%d: expected to fail, but did not", i)
   284  				continue
   285  			}
   286  		}
   287  		f := mod.ToObject(vm).Get(tc.field)
   288  		if f == nil {
   289  			t.Errorf("%v: field %q not found", i, tc.field)
   290  			continue
   291  		}
   292  		value := f.String()
   293  		if value != tc.value {
   294  			t.Errorf("%v: got %q expected %q", i, value, tc.value)
   295  		}
   296  	}
   297  }
   298  
   299  func TestRequireCycle(t *testing.T) {
   300  	vm := js.New()
   301  	r := NewRegistry(WithLoader(mapFileSystemSourceLoader(map[string]string{
   302  		"a.js": `var b = require('./b.js'); exports.done = true;`,
   303  		"b.js": `var a = require('./a.js'); exports.done = true;`,
   304  	})))
   305  	r.Enable(vm)
   306  	res, err := vm.RunString(`
   307  	var a = require('./a.js');
   308  	var b = require('./b.js');
   309  	a.done && b.done;
   310  	`)
   311  	if err != nil {
   312  		t.Fatal(err)
   313  	}
   314  	if v := res.Export(); v != true {
   315  		t.Fatalf("Unexpected result: %v", v)
   316  	}
   317  }
   318  
   319  func TestErrorPropagation(t *testing.T) {
   320  	vm := js.New()
   321  	r := NewRegistry(WithLoader(mapFileSystemSourceLoader(map[string]string{
   322  		"m.js": `throw 'test passed';`,
   323  	})))
   324  	rr := r.Enable(vm)
   325  	_, err := rr.Require("./m")
   326  	if err == nil {
   327  		t.Fatal("Expected an error")
   328  	}
   329  	if ex, ok := err.(*js.Exception); ok {
   330  		if !ex.Value().StrictEquals(vm.ToValue("test passed")) {
   331  			t.Fatalf("Unexpected Exception: %v", ex)
   332  		}
   333  	} else {
   334  		t.Fatal(err)
   335  	}
   336  }
   337  
   338  func TestSourceMapLoader(t *testing.T) {
   339  	vm := js.New()
   340  	r := NewRegistry(WithLoader(func(p string) ([]byte, error) {
   341  		switch p {
   342  		case "dir/m.js":
   343  			return []byte(`throw 'test passed';
   344  //# sourceMappingURL=m.js.map`), nil
   345  		case "dir/m.js.map":
   346  			return []byte(`{"version":3,"file":"m.js","sourceRoot":"","sources":["m.ts"],"names":[],"mappings":";AAAA"}
   347  `), nil
   348  		}
   349  		return nil, ModuleFileDoesNotExistError
   350  	}))
   351  
   352  	rr := r.Enable(vm)
   353  	_, err := rr.Require("./dir/m")
   354  	if err == nil {
   355  		t.Fatal("Expected an error")
   356  	}
   357  	if ex, ok := err.(*js.Exception); ok {
   358  		if !ex.Value().StrictEquals(vm.ToValue("test passed")) {
   359  			t.Fatalf("Unexpected Exception: %v", ex)
   360  		}
   361  	} else {
   362  		t.Fatal(err)
   363  	}
   364  }
   365  
   366  func testsetup() (string, func(), error) {
   367  	name, err := os.MkdirTemp("", "goja-nodejs-require-test")
   368  	if err != nil {
   369  		return "", nil, err
   370  	}
   371  	return name, func() {
   372  		os.RemoveAll(name)
   373  	}, nil
   374  }
   375  
   376  func TestDefaultModuleLoader(t *testing.T) {
   377  	workdir, teardown, err := testsetup()
   378  	if err != nil {
   379  		t.Fatal(err)
   380  	}
   381  	defer teardown()
   382  
   383  	err = os.Chdir(workdir)
   384  	if err != nil {
   385  		t.Fatal(err)
   386  	}
   387  	err = os.Mkdir("module", 0755)
   388  	if err != nil {
   389  		t.Fatal(err)
   390  	}
   391  	err = os.WriteFile("module/index.js", []byte(`throw 'test passed';`), 0644)
   392  	if err != nil {
   393  		t.Fatal(err)
   394  	}
   395  	vm := js.New()
   396  	r := NewRegistry()
   397  	rr := r.Enable(vm)
   398  	_, err = rr.Require("./module")
   399  	if err == nil {
   400  		t.Fatal("Expected an error")
   401  	}
   402  	if ex, ok := err.(*js.Exception); ok {
   403  		if !ex.Value().StrictEquals(vm.ToValue("test passed")) {
   404  			t.Fatalf("Unexpected Exception: %v", ex)
   405  		}
   406  	} else {
   407  		t.Fatal(err)
   408  	}
   409  }