mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-04 07:45:30 +00:00
704 lines
17 KiB
Go
704 lines
17 KiB
Go
package engine
|
|
|
|
import (
|
|
"crypto/md5"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/GopeedLab/gopeed/internal/test"
|
|
"github.com/GopeedLab/gopeed/pkg/base"
|
|
gojaerror "github.com/GopeedLab/gopeed/pkg/download/engine/inject/error"
|
|
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/file"
|
|
gojautil "github.com/GopeedLab/gopeed/pkg/download/engine/util"
|
|
"github.com/dop251/goja"
|
|
)
|
|
|
|
func TestPolyfill(t *testing.T) {
|
|
doTestPolyfill(t, "MessageError")
|
|
doTestPolyfill(t, "XMLHttpRequest")
|
|
doTestPolyfill(t, "Blob")
|
|
doTestPolyfill(t, "Event")
|
|
doTestPolyfill(t, "EventTarget")
|
|
doTestPolyfill(t, "FormData")
|
|
doTestPolyfill(t, "AbortController")
|
|
doTestPolyfill(t, "AbortSignal")
|
|
doTestPolyfill(t, "TextDecoder")
|
|
doTestPolyfill(t, "TextEncoder")
|
|
doTestPolyfill(t, "fetch")
|
|
doTestPolyfill(t, "__gopeed_create_vm")
|
|
}
|
|
|
|
func TestError(t *testing.T) {
|
|
engine := NewEngine(nil)
|
|
_, err := engine.RunString(`
|
|
throw new MessageError('test');
|
|
`)
|
|
if me, ok := gojautil.AssertError[*gojaerror.MessageError](err); !ok {
|
|
t.Fatalf("expect MessageError, but got %v", me)
|
|
}
|
|
}
|
|
|
|
func TestCallFunction_DetachedAsyncWorkDoesNotBlockReturn(t *testing.T) {
|
|
engine := NewEngine(nil)
|
|
defer engine.Close()
|
|
if _, err := engine.RunString(`
|
|
globalThis.__done = false;
|
|
async function testDetached() {
|
|
(async () => {
|
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
globalThis.__done = true;
|
|
})();
|
|
return "ok";
|
|
}
|
|
`); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
fn, ok := goja.AssertFunction(engine.Runtime.Get("testDetached"))
|
|
if !ok {
|
|
t.Fatal("testDetached is not callable")
|
|
}
|
|
startedAt := time.Now()
|
|
value, err := engine.CallFunction(fn)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
elapsed := time.Since(startedAt)
|
|
if elapsed > 200*time.Millisecond {
|
|
t.Fatalf("detached async work blocked return for %s", elapsed)
|
|
}
|
|
if value != "ok" {
|
|
t.Fatalf("unexpected return value: %#v", value)
|
|
}
|
|
}
|
|
|
|
func TestCallFunction_AwaitedPromiseBlocksUntilSettled(t *testing.T) {
|
|
engine := NewEngine(nil)
|
|
defer engine.Close()
|
|
if _, err := engine.RunString(`
|
|
async function testAwaited() {
|
|
await new Promise((resolve) => setTimeout(resolve, 120));
|
|
return "ok";
|
|
}
|
|
`); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
fn, ok := goja.AssertFunction(engine.Runtime.Get("testAwaited"))
|
|
if !ok {
|
|
t.Fatal("testAwaited is not callable")
|
|
}
|
|
startedAt := time.Now()
|
|
value, err := engine.CallFunction(fn)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
elapsed := time.Since(startedAt)
|
|
if elapsed < 100*time.Millisecond {
|
|
t.Fatalf("awaited promise returned too early: %s", elapsed)
|
|
}
|
|
if value != "ok" {
|
|
t.Fatalf("unexpected return value: %#v", value)
|
|
}
|
|
}
|
|
|
|
func TestFetch(t *testing.T) {
|
|
server := startServer()
|
|
defer server.Close()
|
|
engine := NewEngine(nil)
|
|
if _, err := engine.RunString(fmt.Sprintf("var host = 'http://%s';", server.Addr().String())); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, err := engine.RunString(`
|
|
async function testGet(){
|
|
const resp = await fetch(host+'/get');
|
|
return resp.status;
|
|
}
|
|
|
|
async function testText(){
|
|
const resp = await fetch(host+'/text',{
|
|
method: 'POST',
|
|
body: 'test'
|
|
});
|
|
return await resp.text();
|
|
}
|
|
|
|
async function testResponseBodyGetReader() {
|
|
const resp = await fetch(host+'/text', {
|
|
method: 'POST',
|
|
body: 'stream-body'
|
|
});
|
|
if (!resp.body || typeof resp.body.getReader !== 'function') {
|
|
throw new Error('response.body.getReader is not available');
|
|
}
|
|
const reader = resp.body.getReader();
|
|
const decoder = new TextDecoder();
|
|
let result = '';
|
|
while (true) {
|
|
const { done, value } = await reader.read();
|
|
if (done) {
|
|
break;
|
|
}
|
|
result += decoder.decode(value, { stream: true });
|
|
}
|
|
result += decoder.decode();
|
|
return result;
|
|
}
|
|
|
|
async function testStreamingFetchResponseBody() {
|
|
const startedAt = Date.now();
|
|
const resp = await fetch(host+'/stream');
|
|
const resolvedAt = Date.now() - startedAt;
|
|
const reader = resp.body.getReader();
|
|
const decoder = new TextDecoder();
|
|
const first = await reader.read();
|
|
const firstAt = Date.now() - startedAt;
|
|
const second = await reader.read();
|
|
const secondAt = Date.now() - startedAt;
|
|
let result = '';
|
|
result += decoder.decode(first.value, { stream: true });
|
|
result += decoder.decode(second.value, { stream: true });
|
|
result += decoder.decode();
|
|
return {
|
|
resolvedAt,
|
|
firstAt,
|
|
secondAt,
|
|
text: result
|
|
};
|
|
}
|
|
|
|
async function testOctetStream(file){
|
|
const resp = await fetch(host+'/octetStream',{
|
|
method: 'POST',
|
|
body: file
|
|
});
|
|
return await resp.text();
|
|
}
|
|
|
|
async function testRedirect() {
|
|
const url = host + '/redirect?num=3'
|
|
return await new Promise((resolve, reject) => {
|
|
fetch(url, {
|
|
method: 'HEAD',
|
|
redirect: 'error',
|
|
}).then(()=>reject())
|
|
|
|
fetch(url, {
|
|
method: 'HEAD',
|
|
redirect: 'follow',
|
|
}).then((res) =>res.headers.has('location') && reject()).catch(() => reject())
|
|
|
|
fetch(url, {
|
|
method: 'HEAD',
|
|
redirect: 'manual',
|
|
}).then((res) => {
|
|
const location = res.headers.get('location');
|
|
location ? resolve(location) : reject()
|
|
}).catch(() => reject())
|
|
})
|
|
}
|
|
|
|
async function testResponseUrl() {
|
|
return new Promise((resolve, reject) => {
|
|
const xhr = new XMLHttpRequest();
|
|
xhr.open('GET', host+'/redirect?num=3');
|
|
xhr.onload = function(){
|
|
if (xhr.responseURL.includes('/redirect?num=0')){
|
|
resolve();
|
|
}else{
|
|
reject();
|
|
}
|
|
};
|
|
xhr.send();
|
|
});
|
|
}
|
|
|
|
async function testFormData(file){
|
|
const formData = new FormData();
|
|
formData.append('name', 'test');
|
|
formData.append('f', file);
|
|
const resp = await fetch(host+'/formData',{
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
return await resp.json();
|
|
}
|
|
|
|
function testHeader(){
|
|
return new Promise((resolve, reject) => {
|
|
const xhr = new XMLHttpRequest();
|
|
xhr.open('GET', host+'/header');
|
|
xhr.setRequestHeader('X-Gopeed-Test', 'test1');
|
|
xhr.setRequestHeader('x-gopeed-test', 'test2');
|
|
xhr.setRequestHeader('x-Gopeed-test', 'test3');
|
|
xhr.onload = function(){
|
|
const testHeader1 = xhr.getResponseHeader("X-Gopeed-Test");
|
|
const testHeader2 = xhr.getResponseHeader("x-gopeed-test");
|
|
const testHeader3 = xhr.getResponseHeader("x-Gopeed-test");
|
|
const expect = 'test1, test2, test3';
|
|
const all = xhr.getAllResponseHeaders();
|
|
if(testHeader1 === expect && testHeader2 === expect && testHeader3 === expect
|
|
&& all.includes('X-Gopeed-Test: '+expect)){
|
|
resolve();
|
|
}else{
|
|
reject();
|
|
}
|
|
};
|
|
xhr.send();
|
|
});
|
|
}
|
|
|
|
function testProgress(){
|
|
return new Promise((resolve, reject) => {
|
|
const xhr = new XMLHttpRequest();
|
|
xhr.open('GET', host+'/get');
|
|
const xhrUploadPromise = new Promise((resolve, reject) => {
|
|
xhr.upload.onprogress = function(e){
|
|
if(e.loaded === e.total){
|
|
resolve();
|
|
}
|
|
}
|
|
});
|
|
const xhrPromise = new Promise((resolve, reject) => {
|
|
xhr.onprogress = function(e){
|
|
if(e.loaded === e.total){
|
|
resolve();
|
|
}
|
|
}
|
|
});
|
|
Promise.all([xhrUploadPromise, xhrPromise]).then(() => {
|
|
resolve();
|
|
});
|
|
xhr.send();
|
|
setTimeout(() => {
|
|
reject('timeout');
|
|
}, 1000);
|
|
});
|
|
}
|
|
|
|
function testAbort(){
|
|
return new Promise((resolve, reject) => {
|
|
const xhr = new XMLHttpRequest();
|
|
xhr.open('GET', host+'/timeout?duration=500');
|
|
xhr.onabort = function() {
|
|
resolve();
|
|
};
|
|
xhr.send();
|
|
setTimeout(() => {
|
|
xhr.abort();
|
|
}, 200);
|
|
setTimeout(() => {
|
|
reject('timeout');
|
|
}, 1000);
|
|
});
|
|
}
|
|
|
|
function testTimeout(){
|
|
return new Promise((resolve, reject) => {
|
|
const xhr = new XMLHttpRequest();
|
|
const t = 500;
|
|
xhr.open('GET', host+'/timeout?duration='+t);
|
|
xhr.timeout = t - 200;
|
|
xhr.onload = function() {
|
|
resolve();
|
|
};
|
|
xhr.ontimeout = function() {
|
|
reject('timeout');
|
|
};
|
|
xhr.send();
|
|
});
|
|
}
|
|
|
|
async function testFingerprint(fingerprint,ua){
|
|
__gopeed_setFingerprint(fingerprint);
|
|
const resp = await fetch(host+'/ua');
|
|
const data = await resp.json();
|
|
if(!data.user_agent.includes(ua)){
|
|
throw new Error('fingerprint test failed, user agent: ' + data.user_agent);
|
|
}
|
|
}
|
|
|
|
async function testFingerprintDefault(){
|
|
await testFingerprint('none', 'Go')
|
|
}
|
|
|
|
async function testFingerprintChrome(){
|
|
await testFingerprint('chrome', 'Chrome')
|
|
}
|
|
|
|
async function testFingerprintFirefox(){
|
|
await testFingerprint('firefox', 'Firefox')
|
|
}
|
|
|
|
async function testFingerprintSafari(){
|
|
await testFingerprint('safari', 'Safari')
|
|
}
|
|
`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
result, err := callTestFun(engine, "testGet")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if result != int64(200) {
|
|
t.Fatalf("testGet failed, want %d, got %d", 200, result)
|
|
}
|
|
|
|
result, err = callTestFun(engine, "testText")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if result != "test" {
|
|
t.Fatalf("testText failed, want %s, got %s", "test", result)
|
|
}
|
|
|
|
result, err = callTestFun(engine, "testResponseBodyGetReader")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if result != "stream-body" {
|
|
t.Fatalf("testResponseBodyGetReader failed, want %s, got %s", "stream-body", result)
|
|
}
|
|
|
|
result, err = callTestFun(engine, "testStreamingFetchResponseBody")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
streaming := result.(map[string]any)
|
|
if streaming["text"] != "chunk-1chunk-2" {
|
|
t.Fatalf("testStreamingFetchResponseBody failed, want %s, got %v", "chunk-1chunk-2", streaming["text"])
|
|
}
|
|
if int(streaming["resolvedAt"].(int64)) >= 120 {
|
|
t.Fatalf("fetch resolved too late, got %dms", streaming["resolvedAt"].(int64))
|
|
}
|
|
if int(streaming["firstAt"].(int64)) >= 120 {
|
|
t.Fatalf("first chunk arrived too late, got %dms", streaming["firstAt"].(int64))
|
|
}
|
|
if int(streaming["secondAt"].(int64)) < 120 {
|
|
t.Fatalf("second chunk arrived too early, got %dms", streaming["secondAt"].(int64))
|
|
}
|
|
|
|
func() {
|
|
jsFile, _, md5 := buildFile(t, engine.Runtime)
|
|
result, err = callTestFun(engine, "testOctetStream", jsFile)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if result != md5 {
|
|
t.Fatalf("testOctetStream failed, want %s, got %s", md5, result)
|
|
}
|
|
}()
|
|
|
|
t.Run("testRedirect", func(t *testing.T) {
|
|
_, err := callTestFun(engine, "testRedirect")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
})
|
|
|
|
t.Run("testResponseUrl", func(t *testing.T) {
|
|
_, err = callTestFun(engine, "testResponseUrl")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
})
|
|
|
|
func() {
|
|
jsFile, goFile, md5 := buildFile(t, engine.Runtime)
|
|
result, err = callTestFun(engine, "testFormData", jsFile)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := map[string]any{
|
|
"name": "test",
|
|
"f": map[string]string{
|
|
"filename": goFile.Name,
|
|
"md5": md5,
|
|
},
|
|
}
|
|
if !test.JsonEqual(result, want) {
|
|
t.Fatalf("testFormData failed, want %v, got %v", want, result)
|
|
}
|
|
}()
|
|
|
|
_, err = callTestFun(engine, "testHeader")
|
|
if err != nil {
|
|
t.Fatal("header test failed", err)
|
|
}
|
|
|
|
_, err = callTestFun(engine, "testProgress")
|
|
if err != nil {
|
|
t.Fatal("progress test failed", err)
|
|
}
|
|
|
|
_, err = callTestFun(engine, "testAbort")
|
|
if err != nil {
|
|
t.Fatal("abort test failed", err)
|
|
}
|
|
|
|
_, err = callTestFun(engine, "testTimeout")
|
|
if err == nil || err.Error() != "timeout" {
|
|
t.Fatalf("timeout test failed, want %s, got %s", "timeout", err)
|
|
}
|
|
|
|
_, err = callTestFun(engine, "testFingerprintChrome")
|
|
if err != nil {
|
|
t.Fatal("testFingerprintChrome test failed", err)
|
|
}
|
|
_, err = callTestFun(engine, "testFingerprintFirefox")
|
|
if err != nil {
|
|
t.Fatal("testFingerprintFirefox test failed", err)
|
|
}
|
|
_, err = callTestFun(engine, "testFingerprintSafari")
|
|
if err != nil {
|
|
t.Fatal("testFingerprintSafari test failed", err)
|
|
}
|
|
}
|
|
|
|
func TestFetchWithProxy(t *testing.T) {
|
|
doTestFetchWithProxy(t, "", "")
|
|
doTestFetchWithProxy(t, "admin", "123")
|
|
}
|
|
|
|
func doTestFetchWithProxy(t *testing.T, usr, pwd string) {
|
|
httpListener := startServer()
|
|
defer httpListener.Close()
|
|
|
|
proxyListener := test.StartSocks5Server(usr, pwd)
|
|
defer proxyListener.Close()
|
|
engine := NewEngine(&Config{
|
|
ProxyConfig: &base.DownloaderProxyConfig{
|
|
Enable: true,
|
|
System: false,
|
|
Scheme: "socks5",
|
|
Host: proxyListener.Addr().String(),
|
|
Usr: usr,
|
|
Pwd: pwd,
|
|
},
|
|
})
|
|
|
|
if _, err := engine.RunString(fmt.Sprintf("var host = 'http://%s';", httpListener.Addr().String())); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
respCode, err := engine.RunString(`
|
|
(async function(){
|
|
const resp = await fetch(host+'/get');
|
|
return resp.status;
|
|
})()
|
|
`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if respCode != int64(200) {
|
|
t.Fatalf("fetch with proxy failed, want %d, got %d", 200, respCode)
|
|
}
|
|
}
|
|
|
|
func TestVm(t *testing.T) {
|
|
engine := NewEngine(nil)
|
|
|
|
value, err := engine.RunString(`
|
|
const vm = __gopeed_create_vm()
|
|
vm.set('a', 1)
|
|
vm.set('b', 2)
|
|
const result = vm.runString('a=a+1;b=b+1;a+b;')
|
|
const out = {
|
|
"a": vm.get('a'),
|
|
"b": vm.get('b'),
|
|
"result": result
|
|
}
|
|
out
|
|
`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := map[string]any{
|
|
"a": 2,
|
|
"b": 3,
|
|
"result": 5,
|
|
}
|
|
if !test.JsonEqual(value, want) {
|
|
t.Fatalf("vm test failed, want %v, got %v", want, value)
|
|
}
|
|
}
|
|
|
|
func TestNonStopLoop(t *testing.T) {
|
|
engine := NewEngine(nil)
|
|
|
|
_, err := engine.RunString(`
|
|
function leak(){
|
|
setInterval(() => {
|
|
},500)
|
|
}
|
|
|
|
function test(){
|
|
leak()
|
|
return new Promise((resolve, reject) => {
|
|
setTimeout(() => {
|
|
resolve('done')
|
|
}, 1000)
|
|
})
|
|
}
|
|
`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
val, err := callTestFun(engine, "test")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
if val != "done" {
|
|
t.Fatalf("infinite loop test failed, want %s, got %s", "done", val)
|
|
}
|
|
}
|
|
|
|
func doTestPolyfill(t *testing.T, module string) {
|
|
value, err := Run(fmt.Sprintf(`
|
|
!!globalThis['%s']
|
|
`, module))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !value.(bool) {
|
|
t.Fatalf("module %s not polyfilled", module)
|
|
}
|
|
}
|
|
|
|
func startServer() net.Listener {
|
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
server := &http.Server{}
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/get", func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte("ok"))
|
|
})
|
|
mux.HandleFunc("/header", func(w http.ResponseWriter, r *http.Request) {
|
|
for k, v := range r.Header {
|
|
if strings.HasPrefix(k, "X-Gopeed") {
|
|
w.Header().Set(k, strings.Join(v, ", "))
|
|
}
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte("ok"))
|
|
})
|
|
mux.HandleFunc("/text", func(w http.ResponseWriter, r *http.Request) {
|
|
buf, _ := io.ReadAll(r.Body)
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write(buf)
|
|
})
|
|
mux.HandleFunc("/stream", func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
if flusher, ok := w.(http.Flusher); ok {
|
|
_, _ = w.Write([]byte("chunk-1"))
|
|
flusher.Flush()
|
|
time.Sleep(150 * time.Millisecond)
|
|
_, _ = w.Write([]byte("chunk-2"))
|
|
flusher.Flush()
|
|
return
|
|
}
|
|
_, _ = w.Write([]byte("chunk-1chunk-2"))
|
|
})
|
|
mux.HandleFunc("/octetStream", func(w http.ResponseWriter, r *http.Request) {
|
|
md5 := calcMd5(r.Body)
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte(md5))
|
|
})
|
|
mux.HandleFunc("/formData", func(w http.ResponseWriter, r *http.Request) {
|
|
err := r.ParseMultipartForm(1024 * 1024 * 30)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
w.Write([]byte(err.Error()))
|
|
return
|
|
}
|
|
result := make(map[string]any)
|
|
for k, v := range r.MultipartForm.Value {
|
|
result[k] = v[0]
|
|
}
|
|
for k, v := range r.MultipartForm.File {
|
|
f, _ := v[0].Open()
|
|
result[k] = map[string]string{
|
|
"filename": v[0].Filename,
|
|
"md5": calcMd5(f),
|
|
}
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
buf, _ := json.Marshal(result)
|
|
w.Write(buf)
|
|
})
|
|
mux.HandleFunc("/timeout", func(w http.ResponseWriter, r *http.Request) {
|
|
duration := r.URL.Query().Get("duration")
|
|
t, _ := strconv.Atoi(duration)
|
|
time.Sleep(time.Duration(t) * time.Millisecond)
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte("ok"))
|
|
})
|
|
mux.HandleFunc("/redirect", func(w http.ResponseWriter, r *http.Request) {
|
|
num := r.URL.Query().Get("num")
|
|
n, _ := strconv.Atoi(num)
|
|
if n > 0 {
|
|
http.Redirect(w, r, fmt.Sprintf("/redirect?num=%d", n-1), http.StatusFound)
|
|
} else {
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte("ok"))
|
|
}
|
|
})
|
|
mux.HandleFunc("/ua", func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
data := map[string]any{
|
|
"user_agent": r.UserAgent(),
|
|
}
|
|
buf, _ := json.Marshal(data)
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write(buf)
|
|
})
|
|
server.Handler = mux
|
|
go server.Serve(listener)
|
|
return listener
|
|
}
|
|
|
|
func buildFile(t *testing.T, runtime *goja.Runtime) (goja.Value, *file.File, string) {
|
|
jsFile, err := file.NewJsFile(runtime)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
f := jsFile.Export().(*file.File)
|
|
data := "test"
|
|
f.Reader = strings.NewReader(data)
|
|
f.Name = "test.txt"
|
|
f.Size = int64(len(data))
|
|
return jsFile, f, calcMd5(strings.NewReader(data))
|
|
}
|
|
|
|
func callTestFun(engine *Engine, fun string, args ...any) (any, error) {
|
|
test, ok := goja.AssertFunction(engine.Runtime.Get(fun))
|
|
if !ok {
|
|
return nil, errors.New("function not found:" + fun)
|
|
}
|
|
return engine.CallFunction(test, args...)
|
|
}
|
|
|
|
func calcMd5(reader io.Reader) string {
|
|
// Open a new hash interface to write to
|
|
hash := md5.New()
|
|
|
|
// Copy the file in the hash interface and check for any error
|
|
if _, err := io.Copy(hash, reader); err != nil {
|
|
return ""
|
|
}
|
|
return hex.EncodeToString(hash.Sum(nil))
|
|
}
|