Files
seanime-docker/seanime-2.9.10/internal/goja/goja_bindings/fetch_test.go
2025-09-20 14:08:38 +01:00

325 lines
7.8 KiB
Go

package goja_bindings
import (
"fmt"
"net/http"
"net/http/httptest"
"seanime/internal/util"
"sync"
"testing"
"time"
"github.com/davecgh/go-spew/spew"
"github.com/dop251/goja"
gojabuffer "github.com/dop251/goja_nodejs/buffer"
gojarequire "github.com/dop251/goja_nodejs/require"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestFetch_ThreadSafety(t *testing.T) {
// Create a test server that simulates different response times
var serverRequestCount int
var serverMu sync.Mutex
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
serverMu.Lock()
serverRequestCount++
currentRequest := serverRequestCount
serverMu.Unlock()
// Simulate varying response times to increase chance of race conditions
time.Sleep(time.Duration(currentRequest%3) * 50 * time.Millisecond)
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, `{"request": %d}`, currentRequest)
}))
defer server.Close()
// Create JavaScript test code that makes concurrent fetch calls
jsCode := fmt.Sprintf(`
const url = %q;
const promises = [];
// Function to make a fetch request and verify response
async function makeFetch(i) {
const response = await fetch(url);
const data = await response.json();
return { index: i, data };
}
// Create multiple concurrent requests
for (let i = 0; i < 50; i++) {
promises.push(makeFetch(i));
}
// Wait for all requests to complete
Promise.all(promises)
`, server.URL)
// Run the code multiple times to increase chance of catching race conditions
for i := 0; i < 5; i++ {
t.Run(fmt.Sprintf("Iteration_%d", i), func(t *testing.T) {
// Create a new VM for each iteration
vm := goja.New()
BindFetch(vm)
// Execute the JavaScript code
v, err := vm.RunString(jsCode)
assert.NoError(t, err)
// Get the Promise
promise, ok := v.Export().(*goja.Promise)
assert.True(t, ok)
// Wait for the Promise to resolve
for promise.State() == goja.PromiseStatePending {
time.Sleep(10 * time.Millisecond)
}
// Verify the Promise resolved successfully
assert.Equal(t, goja.PromiseStateFulfilled, promise.State())
// Verify we got an array of results
results, ok := promise.Result().Export().([]interface{})
assert.True(t, ok)
assert.Len(t, results, 50)
// Verify each result has the expected structure
for _, result := range results {
resultMap, ok := result.(map[string]interface{})
assert.True(t, ok)
assert.Contains(t, resultMap, "index")
assert.Contains(t, resultMap, "data")
data, ok := resultMap["data"].(map[string]interface{})
assert.True(t, ok)
assert.Contains(t, data, "request")
}
})
}
}
func TestFetch_VMIsolation(t *testing.T) {
// Create a test server
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprint(w, `{"test": "data"}`)
}))
defer server.Close()
// Create multiple VMs and make concurrent requests
const numVMs = 5
const requestsPerVM = 40
var wg sync.WaitGroup
for i := 0; i < numVMs; i++ {
wg.Add(1)
go func(vmIndex int) {
defer wg.Done()
// Create a new VM for this goroutine
vm := goja.New()
BindFetch(vm)
// Create JavaScript code that makes multiple requests
jsCode := fmt.Sprintf(`
const url = %q;
const promises = [];
for (let i = 0; i < %d; i++) {
promises.push(fetch(url).then(r => r.json()));
}
Promise.all(promises)
`, server.URL, requestsPerVM)
// Execute the code
v, err := vm.RunString(jsCode)
assert.NoError(t, err)
// Get and wait for the Promise
promise := v.Export().(*goja.Promise)
for promise.State() == goja.PromiseStatePending {
time.Sleep(10 * time.Millisecond)
}
// Verify the Promise resolved successfully
assert.Equal(t, goja.PromiseStateFulfilled, promise.State())
// Verify we got the expected number of results
results := promise.Result().Export().([]interface{})
assert.Len(t, results, requestsPerVM)
}(i)
}
wg.Wait()
}
func TestGojaPromiseAll(t *testing.T) {
vm := goja.New()
BindFetch(vm)
registry := new(gojarequire.Registry)
registry.Enable(vm)
gojabuffer.Enable(vm)
BindConsole(vm, util.NewLogger())
_, err := vm.RunString(`
async function run() {
const [a, b, c] = await Promise.all([
fetch("https://jsonplaceholder.typicode.com/todos/1"),
fetch("https://jsonplaceholder.typicode.com/todos/2"),
fetch("https://jsonplaceholder.typicode.com/todos/3"),
fetch("https://jsonplaceholder.typicode.com/todos/4"),
fetch("https://jsonplaceholder.typicode.com/todos/5"),
fetch("https://jsonplaceholder.typicode.com/todos/6"),
fetch("https://jsonplaceholder.typicode.com/todos/7"),
fetch("https://jsonplaceholder.typicode.com/todos/8"),
])
const dataA = await a.json();
const dataB = await b.json();
const dataC = await c.json();
console.log("Data A:", dataA.title);
console.log("Data B:", dataB);
console.log("Data C:", dataC);
}
`)
require.NoError(t, err)
runFunc, ok := goja.AssertFunction(vm.Get("run"))
require.True(t, ok)
ret, err := runFunc(goja.Undefined())
require.NoError(t, err)
promise := ret.Export().(*goja.Promise)
for promise.State() == goja.PromiseStatePending {
time.Sleep(10 * time.Millisecond)
}
}
func TestGojaFormDataAndFetch(t *testing.T) {
vm := goja.New()
BindFetch(vm)
registry := new(gojarequire.Registry)
registry.Enable(vm)
gojabuffer.Enable(vm)
BindConsole(vm, util.NewLogger())
_, err := vm.RunString(`
async function run() {
const formData = new FormData();
formData.append("username", "John");
formData.append("accountnum", 123456);
console.log(formData.get("username")); // John
const fData = new URLSearchParams();
for (const pair of formData.entries()) {
fData.append(pair[0], pair[1]);
}
const response = await fetch('https://httpbin.org/post', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: formData
});
const data = await response.json();
console.log(data);
console.log("Echoed GojaFormData content:");
if (data.form) {
for (const key in data.form) {
console.log(key, data.form[key]);
}
} else {
console.log("No form data echoed in the response.");
}
return data;
}
`)
require.NoError(t, err)
runFunc, ok := goja.AssertFunction(vm.Get("run"))
require.True(t, ok)
ret, err := runFunc(goja.Undefined())
require.NoError(t, err)
promise := ret.Export().(*goja.Promise)
for promise.State() == goja.PromiseStatePending {
time.Sleep(10 * time.Millisecond)
}
if promise.State() == goja.PromiseStateFulfilled {
spew.Dump(promise.Result())
} else {
err := promise.Result()
spew.Dump(err)
}
}
func TestGojaFetchPostJSON(t *testing.T) {
vm := goja.New()
BindFetch(vm)
registry := new(gojarequire.Registry)
registry.Enable(vm)
gojabuffer.Enable(vm)
BindConsole(vm, util.NewLogger())
_, err := vm.RunString(`
async function run() {
const response = await fetch('https://httpbin.org/post', {
method: 'POST',
body: { name: "John Doe", age: 30 },
});
const data = await response.json();
console.log(data);
console.log("Echoed content:");
if (data.json) {
for (const key in data.json) {
console.log(key, data.json[key]);
}
} else {
console.log("No form data echoed in the response.");
}
return data;
}
`)
require.NoError(t, err)
runFunc, ok := goja.AssertFunction(vm.Get("run"))
require.True(t, ok)
ret, err := runFunc(goja.Undefined())
require.NoError(t, err)
promise := ret.Export().(*goja.Promise)
for promise.State() == goja.PromiseStatePending {
time.Sleep(10 * time.Millisecond)
}
if promise.State() == goja.PromiseStateFulfilled {
spew.Dump(promise.Result())
} else {
err := promise.Result()
spew.Dump(err)
}
}