mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-04 15:49:49 +00:00
398 lines
8.7 KiB
Go
398 lines
8.7 KiB
Go
package engine
|
|
|
|
import (
|
|
"crypto/md5"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"github.com/GopeedLab/gopeed/internal/test"
|
|
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/GopeedLab/gopeed/pkg/util"
|
|
"github.com/dop251/goja"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestPolyfill(t *testing.T) {
|
|
doTestPolyfill(t, "MessageError")
|
|
doTestPolyfill(t, "XMLHttpRequest")
|
|
doTestPolyfill(t, "Blob")
|
|
doTestPolyfill(t, "FormData")
|
|
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 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 testOctetStream(file){
|
|
const resp = await fetch(host+'/octetStream',{
|
|
method: 'POST',
|
|
body: file
|
|
});
|
|
return await resp.text();
|
|
}
|
|
|
|
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 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();
|
|
});
|
|
}
|
|
`)
|
|
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)
|
|
}
|
|
|
|
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)
|
|
}
|
|
}()
|
|
|
|
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, "testProgress")
|
|
if err != nil {
|
|
t.Fatal("progress test failed")
|
|
}
|
|
|
|
_, err = callTestFun(engine, "testAbort")
|
|
if err != nil {
|
|
t.Fatal("abort test failed")
|
|
}
|
|
|
|
_, err = callTestFun(engine, "testTimeout")
|
|
if err == nil || err.Error() != "timeout" {
|
|
t.Fatalf("timeout test failed, want %s, got %s", "timeout", 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{ProxyURL: util.BuildProxyUrl("socks5", proxyListener.Addr().String(), usr, 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("/text", func(w http.ResponseWriter, r *http.Request) {
|
|
buf, _ := io.ReadAll(r.Body)
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write(buf)
|
|
})
|
|
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"))
|
|
})
|
|
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))
|
|
}
|