Files
seanime-docker/seanime-2.9.10/internal/extension_repo/goja_plugin_system_test.go
2025-09-20 14:08:38 +01:00

2037 lines
66 KiB
Go

package extension_repo
import (
"archive/zip"
"fmt"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"seanime/internal/extension"
"seanime/internal/plugin"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestGojaPluginSystemOS tests the $os bindings in the Goja plugin system
func TestGojaPluginSystemOS(t *testing.T) {
// Create a temporary directory for testing
tempDir := t.TempDir()
// Create test files and directories
testFilePath := filepath.Join(tempDir, "test.txt")
testDirPath := filepath.Join(tempDir, "testdir")
testContent := []byte("Hello, world!")
err := os.WriteFile(testFilePath, testContent, 0644)
require.NoError(t, err)
err = os.Mkdir(testDirPath, 0755)
require.NoError(t, err)
// Test $os.platform and $os.arch
payload := `
function init() {
$ui.register((ctx) => {
console.log("Testing $os bindings");
// Test platform and arch
console.log("Platform:", $os.platform);
console.log("Arch:", $os.arch);
// Test tempDir
const tempDirPath = $os.tempDir();
console.log("Temp dir:", tempDirPath);
// Test readFile
const content = $os.readFile("${TEST_FILE_PATH}");
console.log("File content:", $toString(content));
$store.set("fileContent", $toString(content));
// Test writeFile
$os.writeFile("${TEST_FILE_PATH}.new", $toBytes("New content"), 0644);
const newContent = $os.readFile("${TEST_FILE_PATH}.new");
console.log("New file content:", $toString(newContent));
$store.set("newFileContent", $toString(newContent));
// Test readDir
const entries = $os.readDir("${TEST_DIR}");
console.log("Directory entries:");
for (const entry of entries) {
console.log(" Entry:", entry.name());
}
$store.set("dirEntries", entries.length);
// Test mkdir
$os.mkdir("${TEST_DIR}/newdir", 0755);
const newEntries = $os.readDir("${TEST_DIR}");
console.log("New directory entries:");
for (const entry of newEntries) {
console.log(" Entry:", entry.name());
}
$store.set("newDirEntries", newEntries.length);
// Test stat
const stats = $os.stat("${TEST_FILE_PATH}");
console.log("File stats:", stats);
$store.set("fileSize", stats.size());
// Test rename
$os.rename("${TEST_FILE_PATH}.new", "${TEST_FILE_PATH}.renamed");
const renamedExists = $os.stat("${TEST_FILE_PATH}.renamed") !== null;
console.log("Renamed file exists:", renamedExists);
$store.set("renamedExists", renamedExists);
// Test remove
$os.remove("${TEST_FILE_PATH}.renamed");
let removeSuccess = true;
try {
$os.stat("${TEST_FILE_PATH}.renamed");
removeSuccess = false;
} catch (e) {
// File should not exist
removeSuccess = true;
}
console.log("Remove success:", removeSuccess);
$store.set("removeSuccess", removeSuccess);
});
}
`
// Replace placeholders with actual paths
payload = strings.ReplaceAll(payload, "${TEST_FILE_PATH}", testFilePath)
payload = strings.ReplaceAll(payload, "${TEST_DIR}", tempDir)
opts := DefaultTestPluginOptions()
opts.Payload = payload
opts.Permissions = extension.PluginPermissions{
Scopes: []extension.PluginPermissionScope{
extension.PluginPermissionSystem,
},
Allow: extension.PluginAllowlist{
ReadPaths: []string{tempDir + "/**/*"},
WritePaths: []string{tempDir + "/**/*"},
},
}
plugin, _, manager, _, _, err := InitTestPlugin(t, opts)
require.NoError(t, err)
// Wait for the plugin to execute
time.Sleep(1 * time.Second)
// Check the store values
fileContent, ok := plugin.store.GetOk("fileContent")
require.True(t, ok, "fileContent should be set in store")
assert.Equal(t, "Hello, world!", fileContent)
newFileContent, ok := plugin.store.GetOk("newFileContent")
require.True(t, ok, "newFileContent should be set in store")
assert.Equal(t, "New content", newFileContent)
dirEntries, ok := plugin.store.GetOk("dirEntries")
require.True(t, ok, "dirEntries should be set in store")
assert.Equal(t, int64(3), dirEntries) // test.txt, test.txt.new and testdir
newDirEntries, ok := plugin.store.GetOk("newDirEntries")
require.True(t, ok, "newDirEntries should be set in store")
assert.Equal(t, int64(4), newDirEntries) // test.txt, test.txt.new, testdir, and newdir
fileSize, ok := plugin.store.GetOk("fileSize")
require.True(t, ok, "fileSize should be set in store")
assert.Equal(t, int64(13), fileSize) // "Hello, world!" is 13 bytes
renamedExists, ok := plugin.store.GetOk("renamedExists")
require.True(t, ok, "renamedExists should be set in store")
assert.True(t, renamedExists.(bool))
removeSuccess, ok := plugin.store.GetOk("removeSuccess")
require.True(t, ok, "removeSuccess should be set in store")
assert.True(t, removeSuccess.(bool))
manager.PrintPluginPoolMetrics(opts.ID)
}
// TestGojaPluginSystemOSUnauthorized tests that unauthorized paths are rejected
func TestGojaPluginSystemOSUnauthorized(t *testing.T) {
// Create a temporary directory for testing
tempDir := t.TempDir()
unauthorizedDir := filepath.Join(os.TempDir(), "unauthorized")
// Ensure the unauthorized directory exists
_ = os.MkdirAll(unauthorizedDir, 0755)
defer os.RemoveAll(unauthorizedDir)
payload := `
function init() {
$ui.register((ctx) => {
console.log("Testing unauthorized $os operations");
// Try to read from unauthorized path
try {
const content = $os.readFile("${UNAUTHORIZED_PATH}/test.txt");
$store.set("unauthorizedRead", false);
} catch (e) {
console.log("Unauthorized read error:", e.message);
$store.set("unauthorizedRead", true);
$store.set("unauthorizedReadError", e.message);
}
// Try to write to unauthorized path
try {
$os.writeFile("${UNAUTHORIZED_PATH}/test.txt", $toBytes("Unauthorized"), 0644);
$store.set("unauthorizedWrite", false);
} catch (e) {
console.log("Unauthorized write error:", e.message);
$store.set("unauthorizedWrite", true);
$store.set("unauthorizedWriteError", e.message);
}
// Try to read directory from unauthorized path
try {
const entries = $os.readDir("${UNAUTHORIZED_PATH}");
$store.set("unauthorizedReadDir", false);
} catch (e) {
console.log("Unauthorized readDir error:", e.message);
$store.set("unauthorizedReadDir", true);
$store.set("unauthorizedReadDirError", e.message);
}
});
}
`
// Replace placeholders with actual paths
payload = strings.ReplaceAll(payload, "${UNAUTHORIZED_PATH}", unauthorizedDir)
opts := DefaultTestPluginOptions()
opts.Payload = payload
opts.Permissions = extension.PluginPermissions{
Scopes: []extension.PluginPermissionScope{
extension.PluginPermissionSystem,
},
Allow: extension.PluginAllowlist{
ReadPaths: []string{tempDir + "/*"},
WritePaths: []string{tempDir + "/*"},
},
}
plugin, _, manager, _, _, err := InitTestPlugin(t, opts)
require.NoError(t, err)
// Wait for the plugin to execute
time.Sleep(1 * time.Second)
// Check that unauthorized operations were rejected
unauthorizedRead, ok := plugin.store.GetOk("unauthorizedRead")
require.True(t, ok, "unauthorizedRead should be set in store")
assert.True(t, unauthorizedRead.(bool))
unauthorizedReadError, ok := plugin.store.GetOk("unauthorizedReadError")
require.True(t, ok, "unauthorizedReadError should be set in store")
assert.Contains(t, unauthorizedReadError.(string), "not authorized for read")
unauthorizedWrite, ok := plugin.store.GetOk("unauthorizedWrite")
require.True(t, ok, "unauthorizedWrite should be set in store")
assert.True(t, unauthorizedWrite.(bool))
unauthorizedWriteError, ok := plugin.store.GetOk("unauthorizedWriteError")
require.True(t, ok, "unauthorizedWriteError should be set in store")
assert.Contains(t, unauthorizedWriteError.(string), "not authorized for write")
unauthorizedReadDir, ok := plugin.store.GetOk("unauthorizedReadDir")
require.True(t, ok, "unauthorizedReadDir should be set in store")
assert.True(t, unauthorizedReadDir.(bool))
unauthorizedReadDirError, ok := plugin.store.GetOk("unauthorizedReadDirError")
require.True(t, ok, "unauthorizedReadDirError should be set in store")
assert.Contains(t, unauthorizedReadDirError.(string), "not authorized for read")
manager.PrintPluginPoolMetrics(opts.ID)
}
// TestGojaPluginSystemOSOpenFile tests the $os.openFile, $os.create functions
func TestGojaPluginSystemOSOpenFile(t *testing.T) {
// Create a temporary directory for testing
tempDir := t.TempDir()
payload := `
function init() {
$ui.register((ctx) => {
console.log("Testing $os.openFile and $os.create");
// Test create
const file = $os.create("${TEMP_DIR}/created.txt");
file.writeString("Created file content");
file.close();
const createdContent = $os.readFile("${TEMP_DIR}/created.txt");
console.log("Created file content:", $toString(createdContent));
$store.set("createdContent", $toString(createdContent));
// Test openFile for reading and writing
const fileRW = $os.openFile("${TEMP_DIR}/rw.txt", $os.O_RDWR | $os.O_CREATE, 0644);
fileRW.writeString("Read-write file content");
fileRW.close();
const fileRead = $os.openFile("${TEMP_DIR}/rw.txt", $os.O_RDONLY, 0644);
const buffer = new Uint8Array(100);
const bytesRead = fileRead.read(buffer);
const content = $toString(buffer.subarray(0, bytesRead));
console.log("Read-write file content:", content);
$store.set("rwContent", content);
fileRead.close();
// Test openFile with append
const fileAppend = $os.openFile("${TEMP_DIR}/rw.txt", $os.O_WRONLY | $os.O_APPEND, 0644);
fileAppend.writeString(" - Appended content");
fileAppend.close();
const appendedContent = $os.readFile("${TEMP_DIR}/rw.txt");
console.log("Appended file content:", $toString(appendedContent));
$store.set("appendedContent", $toString(appendedContent));
});
}
`
// Replace placeholders with actual paths
payload = strings.ReplaceAll(payload, "${TEMP_DIR}", tempDir)
opts := DefaultTestPluginOptions()
opts.Payload = payload
opts.Permissions = extension.PluginPermissions{
Scopes: []extension.PluginPermissionScope{
extension.PluginPermissionSystem,
},
Allow: extension.PluginAllowlist{
ReadPaths: []string{tempDir + "/*"},
WritePaths: []string{tempDir + "/*"},
},
}
plugin, _, manager, _, _, err := InitTestPlugin(t, opts)
require.NoError(t, err)
// Wait for the plugin to execute
time.Sleep(1 * time.Second)
// Check the store values
createdContent, ok := plugin.store.GetOk("createdContent")
require.True(t, ok, "createdContent should be set in store")
assert.Equal(t, "Created file content", createdContent)
rwContent, ok := plugin.store.GetOk("rwContent")
require.True(t, ok, "rwContent should be set in store")
assert.Equal(t, "Read-write file content", rwContent)
appendedContent, ok := plugin.store.GetOk("appendedContent")
require.True(t, ok, "appendedContent should be set in store")
assert.Equal(t, "Read-write file content - Appended content", appendedContent)
manager.PrintPluginPoolMetrics(opts.ID)
}
// TestGojaPluginSystemOSMkdirAll tests the $os.mkdirAll function
func TestGojaPluginSystemOSMkdirAll(t *testing.T) {
// Create a temporary directory for testing
tempDir := t.TempDir()
payload := `
function init() {
$ui.register((ctx) => {
console.log("Testing $os.mkdirAll");
// Test mkdirAll with nested directories
$os.mkdirAll("${TEMP_DIR}/nested/dirs/structure", 0755);
// Check if the directories were created
const nestedExists = $os.stat("${TEMP_DIR}/nested") !== null;
const dirsExists = $os.stat("${TEMP_DIR}/nested/dirs") !== null;
const structureExists = $os.stat("${TEMP_DIR}/nested/dirs/structure") !== null;
console.log("Nested directories exist:", nestedExists, dirsExists, structureExists);
$store.set("nestedExists", nestedExists);
$store.set("dirsExists", dirsExists);
$store.set("structureExists", structureExists);
// Create a file in the nested directory
$os.writeFile("${TEMP_DIR}/nested/dirs/structure/test.txt", $toBytes("Nested file"), 0644);
const nestedContent = $os.readFile("${TEMP_DIR}/nested/dirs/structure/test.txt");
console.log("Nested file content:", $toString(nestedContent));
$store.set("nestedContent", $toString(nestedContent));
// Test removeAll
$os.removeAll("${TEMP_DIR}/nested");
let removeAllSuccess = true;
try {
$os.stat("${TEMP_DIR}/nested");
removeAllSuccess = false;
} catch (e) {
// Directory should not exist
removeAllSuccess = true;
}
console.log("RemoveAll success:", removeAllSuccess);
$store.set("removeAllSuccess", removeAllSuccess);
});
}
`
// Replace placeholders with actual paths
payload = strings.ReplaceAll(payload, "${TEMP_DIR}", tempDir)
opts := DefaultTestPluginOptions()
opts.Payload = payload
opts.Permissions = extension.PluginPermissions{
Scopes: []extension.PluginPermissionScope{
extension.PluginPermissionSystem,
},
Allow: extension.PluginAllowlist{
ReadPaths: []string{tempDir + "/**/*"},
WritePaths: []string{tempDir + "/**/*"},
},
}
plugin, _, manager, _, _, err := InitTestPlugin(t, opts)
require.NoError(t, err)
// Wait for the plugin to execute
time.Sleep(1 * time.Second)
// Check the store values
nestedExists, ok := plugin.store.GetOk("nestedExists")
require.True(t, ok, "nestedExists should be set in store")
assert.True(t, nestedExists.(bool))
dirsExists, ok := plugin.store.GetOk("dirsExists")
require.True(t, ok, "dirsExists should be set in store")
assert.True(t, dirsExists.(bool))
structureExists, ok := plugin.store.GetOk("structureExists")
require.True(t, ok, "structureExists should be set in store")
assert.True(t, structureExists.(bool))
nestedContent, ok := plugin.store.GetOk("nestedContent")
require.True(t, ok, "nestedContent should be set in store")
assert.Equal(t, "Nested file", nestedContent)
removeAllSuccess, ok := plugin.store.GetOk("removeAllSuccess")
require.True(t, ok, "removeAllSuccess should be set in store")
assert.True(t, removeAllSuccess.(bool))
manager.PrintPluginPoolMetrics(opts.ID)
}
// TestGojaPluginSystemOSPermissions tests that the plugin system enforces permissions correctly
func TestGojaPluginSystemOSPermissions(t *testing.T) {
payload := `
function init() {
$ui.register((ctx) => {
console.log("Testing $os permissions");
// Try to use $os without system permission
try {
const tempDirPath = $os.tempDir();
$store.set("noPermissionAccess", true);
} catch (e) {
console.log("No permission error:", e.message);
$store.set("noPermissionAccess", false);
$store.set("noPermissionError", e.message);
}
});
}
`
opts := DefaultTestPluginOptions()
opts.Payload = payload
// Deliberately NOT including the system permission
opts.Permissions = extension.PluginPermissions{
Scopes: []extension.PluginPermissionScope{},
}
plugin, _, manager, _, _, err := InitTestPlugin(t, opts)
require.NoError(t, err)
// Wait for the plugin to execute
time.Sleep(1 * time.Second)
// Check that operations were rejected due to missing permissions
noPermissionAccess, ok := plugin.store.GetOk("noPermissionAccess")
require.True(t, ok, "noPermissionAccess should be set in store")
assert.False(t, noPermissionAccess.(bool))
noPermissionError, ok := plugin.store.GetOk("noPermissionError")
require.True(t, ok, "noPermissionError should be set in store")
assert.Contains(t, noPermissionError.(string), "$os is not defined")
manager.PrintPluginPoolMetrics(opts.ID)
}
// TestGojaPluginSystemFilepath tests the $filepath bindings in the Goja plugin system
func TestGojaPluginSystemFilepath(t *testing.T) {
// Create a temporary directory for testing
tempDir := t.TempDir()
// Create test files and directories
testFilePath := filepath.Join(tempDir, "test.txt")
nestedDir := filepath.Join(tempDir, "nested", "dir")
err := os.MkdirAll(nestedDir, 0755)
require.NoError(t, err)
err = os.WriteFile(testFilePath, []byte("Hello, world!"), 0644)
require.NoError(t, err)
payload := `
function init() {
$ui.register((ctx) => {
console.log("Testing $filepath bindings");
// Test base
const baseName = $filepath.base("${TEST_FILE_PATH}");
console.log("Base name:", baseName);
$store.set("baseName", baseName);
// Test dir
const dirName = $filepath.dir("${TEST_FILE_PATH}");
console.log("Dir name:", dirName);
$store.set("dirName", dirName);
// Test ext
const extName = $filepath.ext("${TEST_FILE_PATH}");
console.log("Ext name:", extName);
$store.set("extName", extName);
// Test join
const joinedPath = $filepath.join("${TEMP_DIR}", "subdir", "file.txt");
console.log("Joined path:", joinedPath);
$store.set("joinedPath", joinedPath);
// Test split
const [dir, file] = $filepath.split("${TEST_FILE_PATH}");
console.log("Split path:", dir, file);
$store.set("splitDir", dir);
$store.set("splitFile", file);
// Test glob
const globResults = $filepath.glob("${TEMP_DIR}", "*.txt");
console.log("Glob results:", globResults);
$store.set("globResults", globResults.length);
// Test match
const isMatch = $filepath.match("*.txt", "test.txt");
console.log("Match result:", isMatch);
$store.set("isMatch", isMatch);
// Test isAbs
const isAbsPath = $filepath.isAbs("${TEST_FILE_PATH}");
console.log("Is absolute path:", isAbsPath);
$store.set("isAbsPath", isAbsPath);
// Test toSlash and fromSlash
const slashPath = $filepath.toSlash("${TEST_FILE_PATH}");
console.log("To slash:", slashPath);
$store.set("slashPath", slashPath);
const fromSlashPath = $filepath.fromSlash(slashPath);
console.log("From slash:", fromSlashPath);
$store.set("fromSlashPath", fromSlashPath);
});
}
`
// Replace placeholders with actual paths
payload = strings.ReplaceAll(payload, "${TEST_FILE_PATH}", testFilePath)
payload = strings.ReplaceAll(payload, "${TEMP_DIR}", tempDir)
opts := DefaultTestPluginOptions()
opts.Payload = payload
opts.Permissions = extension.PluginPermissions{
Scopes: []extension.PluginPermissionScope{
extension.PluginPermissionSystem,
},
Allow: extension.PluginAllowlist{
ReadPaths: []string{tempDir + "/**/*"},
WritePaths: []string{tempDir + "/**/*"},
},
}
plugin, _, manager, _, _, err := InitTestPlugin(t, opts)
require.NoError(t, err)
// Wait for the plugin to execute
time.Sleep(1 * time.Second)
// Check the store values
baseName, ok := plugin.store.GetOk("baseName")
require.True(t, ok, "baseName should be set in store")
assert.Equal(t, "test.txt", baseName)
dirName, ok := plugin.store.GetOk("dirName")
require.True(t, ok, "dirName should be set in store")
assert.Equal(t, tempDir, dirName)
extName, ok := plugin.store.GetOk("extName")
require.True(t, ok, "extName should be set in store")
assert.Equal(t, ".txt", extName)
joinedPath, ok := plugin.store.GetOk("joinedPath")
require.True(t, ok, "joinedPath should be set in store")
assert.Equal(t, filepath.Join(tempDir, "subdir", "file.txt"), joinedPath)
splitDir, ok := plugin.store.GetOk("splitDir")
require.True(t, ok, "splitDir should be set in store")
assert.Equal(t, tempDir+string(filepath.Separator), splitDir)
splitFile, ok := plugin.store.GetOk("splitFile")
require.True(t, ok, "splitFile should be set in store")
assert.Equal(t, "test.txt", splitFile)
globResults, ok := plugin.store.GetOk("globResults")
require.True(t, ok, "globResults should be set in store")
assert.Equal(t, int64(1), globResults) // test.txt
isMatch, ok := plugin.store.GetOk("isMatch")
require.True(t, ok, "isMatch should be set in store")
assert.True(t, isMatch.(bool))
isAbsPath, ok := plugin.store.GetOk("isAbsPath")
require.True(t, ok, "isAbsPath should be set in store")
assert.True(t, isAbsPath.(bool))
slashPath, ok := plugin.store.GetOk("slashPath")
require.True(t, ok, "slashPath should be set in store")
assert.Contains(t, slashPath.(string), "/")
fromSlashPath, ok := plugin.store.GetOk("fromSlashPath")
require.True(t, ok, "fromSlashPath should be set in store")
assert.Equal(t, testFilePath, fromSlashPath)
manager.PrintPluginPoolMetrics(opts.ID)
}
// TestGojaPluginSystemIO tests the $io bindings in the Goja plugin system
func TestGojaPluginSystemIO(t *testing.T) {
// Create a temporary directory for testing
tempDir := t.TempDir()
// Create test file
testFilePath := filepath.Join(tempDir, "test.txt")
err := os.WriteFile(testFilePath, []byte("Hello, world!"), 0644)
require.NoError(t, err)
payload := `
function init() {
$ui.register((ctx) => {
console.log("Testing $io bindings");
// Test readAll
const file = $os.openFile("${TEST_FILE_PATH}", $os.O_RDONLY, 0);
const content = $io.readAll(file);
file.close();
console.log("Read content:", $toString(content));
$store.set("readAllContent", $toString(content));
// Test copy
const srcFile = $os.openFile("${TEST_FILE_PATH}", $os.O_RDONLY, 0);
const destFile = $os.create("${TEMP_DIR}/copy.txt");
const bytesCopied = $io.copy(destFile, srcFile);
srcFile.close();
destFile.close();
console.log("Bytes copied:", bytesCopied);
$store.set("bytesCopied", bytesCopied);
// Test writeString
const stringFile = $os.create("${TEMP_DIR}/string.txt");
const bytesWritten = $io.writeString(stringFile, "Written with writeString");
stringFile.close();
console.log("Bytes written:", bytesWritten);
$store.set("bytesWritten", bytesWritten);
// Read the file back to verify
const stringContent = $os.readFile("${TEMP_DIR}/string.txt");
console.log("String content:", $toString(stringContent));
$store.set("stringContent", $toString(stringContent));
// Test copyN
const srcFileN = $os.openFile("${TEST_FILE_PATH}", $os.O_RDONLY, 0);
const destFileN = $os.create("${TEMP_DIR}/copyN.txt");
const bytesCopiedN = $io.copyN(destFileN, srcFileN, 5); // Copy only 5 bytes
srcFileN.close();
destFileN.close();
console.log("Bytes copied with copyN:", bytesCopiedN);
$store.set("bytesCopiedN", bytesCopiedN);
// Read the file back to verify
const copyNContent = $os.readFile("${TEMP_DIR}/copyN.txt");
console.log("CopyN content:", $toString(copyNContent));
$store.set("copyNContent", $toString(copyNContent));
// Test limitReader
const bigFile = $os.openFile("${TEST_FILE_PATH}", $os.O_RDONLY, 0);
const limitedReader = $io.limitReader(bigFile, 5); // Limit to 5 bytes
const limitBuffer = new Uint8Array(100);
const limitBytesRead = limitedReader.read(limitBuffer);
bigFile.close();
console.log("Limited bytes read:", limitBytesRead);
$store.set("limitBytesRead", limitBytesRead);
$store.set("limitContent", $toString(limitBuffer.subarray(0, limitBytesRead)));
});
}
`
// Replace placeholders with actual paths
payload = strings.ReplaceAll(payload, "${TEST_FILE_PATH}", testFilePath)
payload = strings.ReplaceAll(payload, "${TEMP_DIR}", tempDir)
opts := DefaultTestPluginOptions()
opts.Payload = payload
opts.Permissions = extension.PluginPermissions{
Scopes: []extension.PluginPermissionScope{
extension.PluginPermissionSystem,
},
Allow: extension.PluginAllowlist{
ReadPaths: []string{tempDir + "/**/*"},
WritePaths: []string{tempDir + "/**/*"},
},
}
plugin, _, manager, _, _, err := InitTestPlugin(t, opts)
require.NoError(t, err)
// Wait for the plugin to execute
time.Sleep(1 * time.Second)
// Check the store values
readAllContent, ok := plugin.store.GetOk("readAllContent")
require.True(t, ok, "readAllContent should be set in store")
assert.Equal(t, "Hello, world!", readAllContent)
bytesCopied, ok := plugin.store.GetOk("bytesCopied")
require.True(t, ok, "bytesCopied should be set in store")
assert.Equal(t, int64(13), bytesCopied) // "Hello, world!" is 13 bytes
bytesWritten, ok := plugin.store.GetOk("bytesWritten")
require.True(t, ok, "bytesWritten should be set in store")
assert.Equal(t, int64(24), bytesWritten) // "Written with writeString" is 24 bytes
stringContent, ok := plugin.store.GetOk("stringContent")
require.True(t, ok, "stringContent should be set in store")
assert.Equal(t, "Written with writeString", stringContent)
bytesCopiedN, ok := plugin.store.GetOk("bytesCopiedN")
require.True(t, ok, "bytesCopiedN should be set in store")
assert.Equal(t, int64(5), bytesCopiedN)
copyNContent, ok := plugin.store.GetOk("copyNContent")
require.True(t, ok, "copyNContent should be set in store")
assert.Equal(t, "Hello", copyNContent)
limitBytesRead, ok := plugin.store.GetOk("limitBytesRead")
require.True(t, ok, "limitBytesRead should be set in store")
assert.Equal(t, int64(5), limitBytesRead)
limitContent, ok := plugin.store.GetOk("limitContent")
require.True(t, ok, "limitContent should be set in store")
assert.Equal(t, "Hello", limitContent)
manager.PrintPluginPoolMetrics(opts.ID)
}
// TestGojaPluginSystemBufio tests the $bufio bindings in the Goja plugin system
func TestGojaPluginSystemBufio(t *testing.T) {
// Create a temporary directory for testing
tempDir := t.TempDir()
// Create test file with multiple lines
testFilePath := filepath.Join(tempDir, "multiline.txt")
err := os.WriteFile(testFilePath, []byte("Line 1\nLine 2\nLine 3\nLine 4\n"), 0644)
require.NoError(t, err)
payload := `
function init() {
$ui.register((ctx) => {
console.log("Testing $bufio bindings");
// Test NewReader and ReadString
const file = $os.openFile("${TEST_FILE_PATH}", $os.O_RDONLY, 0);
const reader = $bufio.newReader(file);
// Read lines one by one with try/catch to handle EOF
const lines = [];
for (let i = 0; i < 10; i++) { // Try to read more lines than exist
try {
const line = reader.readString($toBytes('\n'));
console.log("Read line:", line);
lines.push(line.trim());
} catch (e) {
console.log("Caught expected EOF:", e.message);
$store.set("eofCaught", true);
}
}
file.close();
console.log("Read lines:", lines);
$store.set("lines", lines);
// Test NewWriter
const writeFile = $os.create("${TEMP_DIR}/bufio_write.txt");
const writer = $bufio.newWriter(writeFile);
// Write multiple strings
writer.writeString("Buffered ");
writer.writeString("write ");
writer.writeString("test");
// Flush to ensure data is written
writer.flush();
writeFile.close();
// Read back the file to verify
const writtenContent = $os.readFile("${TEMP_DIR}/bufio_write.txt");
console.log("Written content:", $toString(writtenContent));
$store.set("writtenContent", $toString(writtenContent));
// Test Scanner
const scanFile = $os.openFile("${TEST_FILE_PATH}", $os.O_RDONLY, 0);
const scanner = $bufio.newScanner(scanFile);
// Scan lines
const scannedLines = [];
while (scanner.scan()) {
scannedLines.push(scanner.text());
}
scanFile.close();
console.log("Scanned lines:", scannedLines);
$store.set("scannedLines", scannedLines);
// Test ReadBytes
try {
const bytesFile = $os.openFile("${TEST_FILE_PATH}", $os.O_RDONLY, 0);
const bytesReader = $bufio.newReader(bytesFile);
const bytesLines = [];
try {
for (let i = 0; i < 10; i++) {
const lineBytes = bytesReader.readBytes('\n'.charCodeAt(0));
bytesLines.push($toString(lineBytes).trim());
}
} catch (e) {
console.log("Caught expected EOF in readBytes:", e.message);
$store.set("eofCaughtBytes", true);
}
bytesFile.close();
console.log("Read bytes lines:", bytesLines);
$store.set("bytesLines", bytesLines);
} catch (e) {
console.log("Error in ReadBytes test:", e.message);
}
});
}
`
// Replace placeholders with actual paths
payload = strings.ReplaceAll(payload, "${TEST_FILE_PATH}", testFilePath)
payload = strings.ReplaceAll(payload, "${TEMP_DIR}", tempDir)
opts := DefaultTestPluginOptions()
opts.Payload = payload
opts.Permissions = extension.PluginPermissions{
Scopes: []extension.PluginPermissionScope{
extension.PluginPermissionSystem,
},
Allow: extension.PluginAllowlist{
ReadPaths: []string{tempDir + "/**/*"},
WritePaths: []string{tempDir + "/**/*"},
},
}
plugin, _, manager, _, _, err := InitTestPlugin(t, opts)
require.NoError(t, err)
// Wait for the plugin to execute
time.Sleep(1 * time.Second)
// Check the store values
lines, ok := plugin.store.GetOk("lines")
require.True(t, ok, "lines should be set in store")
assert.Equal(t, []interface{}{"Line 1", "Line 2", "Line 3", "Line 4"}, lines)
eofCaught, ok := plugin.store.GetOk("eofCaught")
require.True(t, ok, "eofCaught should be set in store")
assert.True(t, eofCaught.(bool))
writtenContent, ok := plugin.store.GetOk("writtenContent")
require.True(t, ok, "writtenContent should be set in store")
assert.Equal(t, "Buffered write test", writtenContent)
scannedLines, ok := plugin.store.GetOk("scannedLines")
require.True(t, ok, "scannedLines should be set in store")
assert.Equal(t, []interface{}{"Line 1", "Line 2", "Line 3", "Line 4"}, scannedLines)
bytesLines, ok := plugin.store.GetOk("bytesLines")
if ok {
assert.Equal(t, []interface{}{"Line 1", "Line 2", "Line 3", "Line 4"}, bytesLines)
}
eofCaughtBytes, ok := plugin.store.GetOk("eofCaughtBytes")
if ok {
assert.True(t, eofCaughtBytes.(bool))
}
manager.PrintPluginPoolMetrics(opts.ID)
}
// TestGojaPluginSystemBytes tests the $bytes bindings in the Goja plugin system
func TestGojaPluginSystemBytes(t *testing.T) {
// Create a temporary directory for testing
tempDir := t.TempDir()
payload := `
function init() {
$ui.register((ctx) => {
console.log("Testing $bytes bindings");
// Test NewBuffer
const buffer = $bytes.newBuffer($toBytes("Hello"));
buffer.writeString(", world!");
const bufferContent = $toString(buffer.bytes());
console.log("Buffer content:", bufferContent);
$store.set("bufferContent", bufferContent);
// Test NewBufferString
const strBuffer = $bytes.newBufferString("String buffer");
strBuffer.writeString(" test");
const strBufferContent = strBuffer.string();
console.log("String buffer content:", strBufferContent);
$store.set("strBufferContent", strBufferContent);
// Test NewReader
const reader = $bytes.newReader($toBytes("Bytes reader test"));
const readerBuffer = new Uint8Array(100);
const bytesRead = reader.read(readerBuffer);
const readerContent = $toString(readerBuffer.subarray(0, bytesRead));
console.log("Reader content:", readerContent);
$store.set("readerContent", readerContent);
// Test buffer methods
const testBuffer = $bytes.newBuffer($toBytes(""));
testBuffer.writeString("Test");
testBuffer.writeByte(32); // Space
testBuffer.writeString("methods");
const testBufferContent = testBuffer.string();
console.log("Test buffer content:", testBufferContent);
$store.set("testBufferContent", testBufferContent);
// Test read methods
const readBuffer = $bytes.newBuffer($toBytes("Read test"));
const readByte = readBuffer.readByte();
console.log("Read byte:", String.fromCharCode(readByte));
$store.set("readByte", readByte);
const nextBytes = new Uint8Array(4);
readBuffer.read(nextBytes);
console.log("Next bytes:", $toString(nextBytes));
$store.set("nextBytes", $toString(nextBytes));
});
}
`
opts := DefaultTestPluginOptions()
opts.Payload = payload
opts.Permissions = extension.PluginPermissions{
Scopes: []extension.PluginPermissionScope{
extension.PluginPermissionSystem,
},
Allow: extension.PluginAllowlist{
ReadPaths: []string{tempDir + "/**/*"},
WritePaths: []string{tempDir + "/**/*"},
},
}
plugin, _, manager, _, _, err := InitTestPlugin(t, opts)
require.NoError(t, err)
// Wait for the plugin to execute
time.Sleep(1 * time.Second)
// Check the store values
bufferContent, ok := plugin.store.GetOk("bufferContent")
require.True(t, ok, "bufferContent should be set in store")
assert.Equal(t, "Hello, world!", bufferContent)
strBufferContent, ok := plugin.store.GetOk("strBufferContent")
require.True(t, ok, "strBufferContent should be set in store")
assert.Equal(t, "String buffer test", strBufferContent)
readerContent, ok := plugin.store.GetOk("readerContent")
require.True(t, ok, "readerContent should be set in store")
assert.Equal(t, "Bytes reader test", readerContent)
testBufferContent, ok := plugin.store.GetOk("testBufferContent")
require.True(t, ok, "testBufferContent should be set in store")
assert.Equal(t, "Test methods", testBufferContent)
readByte, ok := plugin.store.GetOk("readByte")
require.True(t, ok, "readByte should be set in store")
assert.Equal(t, int64('R'), readByte)
nextBytes, ok := plugin.store.GetOk("nextBytes")
require.True(t, ok, "nextBytes should be set in store")
assert.Equal(t, "ead ", nextBytes)
manager.PrintPluginPoolMetrics(opts.ID)
}
// TestGojaPluginSystemDownloader tests the ctx.downloader bindings in the Goja plugin system
func TestGojaPluginSystemDownloader(t *testing.T) {
// Create a temporary directory for testing
tempDir := t.TempDir()
// Create a test HTTP server that serves a large file in chunks to simulate download progress
const totalSize = 1024 * 1024 // 1MB
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Set content length for proper progress calculation
w.Header().Set("Content-Length", fmt.Sprintf("%d", totalSize))
// Flush headers to client
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
// Send data in chunks with delays to simulate download progress
chunkSize := 32 * 1024 // 32KB chunks
chunk := make([]byte, chunkSize)
for i := 0; i < len(chunk); i++ {
chunk[i] = byte(i % 256)
}
for sent := 0; sent < totalSize; sent += chunkSize {
// Sleep to simulate network delay
time.Sleep(100 * time.Millisecond)
// Calculate remaining bytes
remaining := totalSize - sent
if remaining < chunkSize {
chunkSize = remaining
}
// Write chunk
w.Write(chunk[:chunkSize])
// Flush to ensure client receives data immediately
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
}
}))
defer server.Close()
payload := `
function init() {
$ui.register((ctx) => {
console.log("Testing ctx.downloader bindings with large file");
// Test download
const downloadPath = "${TEMP_DIR}/large_download.bin";
try {
const downloadID = ctx.downloader.download("${SERVER_URL}", downloadPath, {
timeout: 60 // 60 second timeout
});
console.log("Download started with ID:", downloadID);
$store.set("downloadID", downloadID);
// Track progress updates
const progressUpdates = [];
// Wait for download to complete
let downloadComplete = ctx.state(false);
const cancelWatch = ctx.downloader.watch(downloadID, (progress) => {
// Store progress update
progressUpdates.push({
percentage: progress.percentage,
totalBytes: progress.totalBytes,
speed: progress.speed,
status: progress.status
});
console.log("Download progress:",
progress.percentage.toFixed(2), "%, ",
"Speed:", (progress.speed / 1024).toFixed(2), "KB/s, ",
"Downloaded:", (progress.totalBytes / 1024).toFixed(2), "KB"
, progress);
if (progress.status === "completed") {
downloadComplete.set(true);
$store.set("downloadComplete", true);
$store.set("downloadProgress", progress);
$store.set("progressUpdates", progressUpdates);
} else if (progress.status === "error") {
console.log("Download error:", progress.error);
$store.set("downloadError", progress.error);
}
});
// Wait for download to complete
ctx.effect(() => {
if (!downloadComplete.get()) {
return
}
// Cancel watch
cancelWatch();
// Check downloaded file
try {
if (downloadComplete) {
const stats = $os.stat(downloadPath);
console.log("Downloaded file size:", stats.size(), "bytes");
$store.set("downloadedSize", stats.size());
}
// List downloads
const downloads = ctx.downloader.listDownloads();
console.log("Active downloads:", downloads.length);
$store.set("downloadsCount", downloads.length);
// Get progress
const progress = ctx.downloader.getProgress(downloadID);
if (progress) {
console.log("Final download progress:", progress);
$store.set("finalProgress", progress);
} else {
console.log("Progress not found for ID:", downloadID);
$store.set("progressNotFound", true);
}
} catch (e) {
console.log("Error in download check:", e.message);
$store.set("checkError", e.message);
}
}, [downloadComplete]);
} catch (e) {
console.log("Error starting download:", e.message);
$store.set("startError", e.message);
}
});
}
`
// Replace placeholders with actual paths
payload = strings.ReplaceAll(payload, "${TEMP_DIR}", tempDir)
payload = strings.ReplaceAll(payload, "${SERVER_URL}", server.URL)
opts := DefaultTestPluginOptions()
opts.Payload = payload
opts.Permissions = extension.PluginPermissions{
Scopes: []extension.PluginPermissionScope{
extension.PluginPermissionSystem,
},
Allow: extension.PluginAllowlist{
ReadPaths: []string{tempDir + "/**/*"},
WritePaths: []string{tempDir + "/**/*"},
},
}
p, logger, manager, _, _, err := InitTestPlugin(t, opts)
require.NoError(t, err)
// Wait for the plugin to execute and download to complete
time.Sleep(12 * time.Second)
// Check the store values
downloadID, ok := p.store.GetOk("downloadID")
require.True(t, ok, "downloadID should be set in store")
assert.NotEmpty(t, downloadID)
// Check if download completed or if there was an error
downloadComplete, ok := p.store.GetOk("downloadComplete")
if ok && downloadComplete.(bool) {
// If download completed, check file size
downloadedSize, ok := p.store.GetOk("downloadedSize")
require.True(t, ok, "downloadedSize should be set in store")
assert.Equal(t, int64(totalSize), downloadedSize)
// Check progress updates
progressUpdates, ok := p.store.GetOk("progressUpdates")
require.True(t, ok, "progressUpdates should be set in store")
updates, ok := progressUpdates.([]interface{})
require.True(t, ok, "progressUpdates should be a slice")
// Should have multiple progress updates
assert.Greater(t, len(updates), 1, "Should have multiple progress updates")
// Print progress updates for debugging
logger.Info().Msgf("Received %d progress updates", len(updates))
for i, update := range updates {
if i < 5 || i >= len(updates)-5 {
logger.Info().Interface("update", update).Msgf("Progress update %d", i)
} else if i == 5 {
logger.Info().Msg("... more updates ...")
}
}
finalProgress, ok := p.store.GetOk("finalProgress")
if ok {
progressMap, ok := finalProgress.(*plugin.DownloadProgress)
require.Truef(t, ok, "finalProgress should be a map, got %T", finalProgress)
assert.Equal(t, "completed", progressMap.Status)
assert.InDelta(t, 100.0, progressMap.Percentage, 0.1)
}
} else {
// If download failed, check error
downloadError, _ := p.store.GetOk("downloadError")
t.Logf("Download error: %v", downloadError)
// Don't fail the test if there was an error, just log it
}
manager.PrintPluginPoolMetrics(opts.ID)
}
// TestGojaPluginSystemFilepathWalk tests the walk and walkDir functions in the filepath module
func TestGojaPluginSystemFilepathWalk(t *testing.T) {
// Create a temporary directory for testing
tempDir := t.TempDir()
// Create a directory structure for testing walk and walkDir
dirs := []string{
filepath.Join(tempDir, "dir1"),
filepath.Join(tempDir, "dir1", "subdir1"),
filepath.Join(tempDir, "dir1", "subdir2"),
filepath.Join(tempDir, "dir2"),
filepath.Join(tempDir, "dir2", "subdir1"),
}
files := []string{
filepath.Join(tempDir, "file1.txt"),
filepath.Join(tempDir, "file2.txt"),
filepath.Join(tempDir, "dir1", "file3.txt"),
filepath.Join(tempDir, "dir1", "subdir1", "file4.txt"),
filepath.Join(tempDir, "dir1", "subdir2", "file5.txt"),
filepath.Join(tempDir, "dir2", "file6.txt"),
filepath.Join(tempDir, "dir2", "subdir1", "file7.txt"),
}
// Create directories
for _, dir := range dirs {
err := os.MkdirAll(dir, 0755)
require.NoError(t, err)
}
// Create files with content
for i, file := range files {
content := fmt.Sprintf("Content of file %d", i+1)
err := os.WriteFile(file, []byte(content), 0644)
require.NoError(t, err)
}
payload := `
function init() {
$ui.register((ctx) => {
console.log("Testing $filepath walk and walkDir");
// Test walk
const walkPaths = [];
const walkErrors = [];
$filepath.walk("${TEMP_DIR}", (path, info, err) => {
if (err) {
console.log("Walk error:", path, err);
walkErrors.push({ path, error: err.message });
return; // Continue walking
}
console.log("Walk path:", path, "isDir:", info.isDir());
walkPaths.push({
path: path,
isDir: info.isDir(),
name: info.name()
});
return; // Continue walking
});
console.log("Walk found", walkPaths.length, "paths");
$store.set("walkPaths", walkPaths);
$store.set("walkErrors", walkErrors);
// Test walkDir
const walkDirPaths = [];
const walkDirErrors = [];
$filepath.walkDir("${TEMP_DIR}", (path, d, err) => {
if (err) {
console.log("WalkDir error:", path, err);
walkDirErrors.push({ path, error: err.message });
return; // Continue walking
}
console.log("WalkDir path:", path, "isDir:", d.isDir());
walkDirPaths.push({
path: path,
isDir: d.isDir(),
name: d.name()
});
return; // Continue walking
});
console.log("WalkDir found", walkDirPaths.length, "paths");
$store.set("walkDirPaths", walkDirPaths);
$store.set("walkDirErrors", walkDirErrors);
// Count files and directories found
const walkFileCount = walkPaths.filter(p => !p.isDir).length;
const walkDirCount = walkPaths.filter(p => p.isDir).length;
const walkDirFileCount = walkDirPaths.filter(p => !p.isDir).length;
const walkDirDirCount = walkDirPaths.filter(p => p.isDir).length;
console.log("Walk found", walkFileCount, "files and", walkDirCount, "directories");
console.log("WalkDir found", walkDirFileCount, "files and", walkDirDirCount, "directories");
$store.set("walkFileCount", walkFileCount);
$store.set("walkDirCount", walkDirCount);
$store.set("walkDirFileCount", walkDirFileCount);
$store.set("walkDirDirCount", walkDirDirCount);
// Test skipping a directory
const skipDirWalkPaths = [];
$filepath.walk("${TEMP_DIR}", (path, info, err) => {
if (err) {
return; // Continue walking
}
// Skip dir1 and its subdirectories
if (info.isDir() && info.name() === "dir1") {
console.log("Skipping directory:", path);
return $filepath.skipDir; // Skip this directory
}
skipDirWalkPaths.push(path);
return; // Continue walking
});
console.log("Skip dir walk found", skipDirWalkPaths.length, "paths");
$store.set("skipDirWalkPaths", skipDirWalkPaths);
});
}
`
// Replace placeholders with actual paths
payload = strings.ReplaceAll(payload, "${TEMP_DIR}", tempDir)
opts := DefaultTestPluginOptions()
opts.Payload = payload
opts.Permissions = extension.PluginPermissions{
Scopes: []extension.PluginPermissionScope{
extension.PluginPermissionSystem,
},
Allow: extension.PluginAllowlist{
ReadPaths: []string{tempDir + "/**/*"},
WritePaths: []string{tempDir + "/**/*"},
},
}
plugin, _, manager, _, _, err := InitTestPlugin(t, opts)
require.NoError(t, err)
// Wait for the plugin to execute
time.Sleep(1 * time.Second)
// Check the store values
walkPaths, ok := plugin.store.GetOk("walkPaths")
require.True(t, ok, "walkPaths should be set in store")
walkPathsSlice, ok := walkPaths.([]interface{})
require.True(t, ok, "walkPaths should be a slice")
// Total number of paths should be dirs + files + root dir
assert.Equal(t, len(dirs)+len(files)+1, len(walkPathsSlice), "walkPaths should contain all directories and files")
walkDirPaths, ok := plugin.store.GetOk("walkDirPaths")
require.True(t, ok, "walkDirPaths should be set in store")
walkDirPathsSlice, ok := walkDirPaths.([]interface{})
require.True(t, ok, "walkDirPaths should be a slice")
// Total number of paths should be dirs + files + root dir
assert.Equal(t, len(dirs)+len(files)+1, len(walkDirPathsSlice), "walkDirPaths should contain all directories and files")
// Check file and directory counts
walkFileCount, ok := plugin.store.GetOk("walkFileCount")
require.True(t, ok, "walkFileCount should be set in store")
assert.Equal(t, int64(len(files)), walkFileCount, "walkFileCount should match the number of files")
walkDirCount, ok := plugin.store.GetOk("walkDirCount")
require.True(t, ok, "walkDirCount should be set in store")
assert.Equal(t, int64(len(dirs)+1), walkDirCount, "walkDirCount should match the number of directories plus root")
walkDirFileCount, ok := plugin.store.GetOk("walkDirFileCount")
require.True(t, ok, "walkDirFileCount should be set in store")
assert.Equal(t, int64(len(files)), walkDirFileCount, "walkDirFileCount should match the number of files")
walkDirDirCount, ok := plugin.store.GetOk("walkDirDirCount")
require.True(t, ok, "walkDirDirCount should be set in store")
assert.Equal(t, int64(len(dirs)+1), walkDirDirCount, "walkDirDirCount should match the number of directories plus root")
// Check skipping directories
skipDirWalkPaths, ok := plugin.store.GetOk("skipDirWalkPaths")
require.True(t, ok, "skipDirWalkPaths should be set in store")
skipDirWalkPathsSlice, ok := skipDirWalkPaths.([]interface{})
require.True(t, ok, "skipDirWalkPaths should be a slice")
// Count how many paths should be left after skipping dir1 and its subdirectories
// We should have tempDir, dir2, dir2/subdir1, and their files (file1.txt, file2.txt, file6.txt, file7.txt)
expectedPathsAfterSkip := 1 + 2 + 4 // root + dir2 dirs + files in root and dir2
assert.Equal(t, expectedPathsAfterSkip, len(skipDirWalkPathsSlice), "skipDirWalkPaths should not contain dir1 and its subdirectories")
// Check for errors
walkErrors, ok := plugin.store.GetOk("walkErrors")
require.True(t, ok, "walkErrors should be set in store")
walkErrorsSlice, ok := walkErrors.([]interface{})
require.True(t, ok, "walkErrors should be a slice")
assert.Empty(t, walkErrorsSlice, "There should be no walk errors")
walkDirErrors, ok := plugin.store.GetOk("walkDirErrors")
require.True(t, ok, "walkDirErrors should be set in store")
walkDirErrorsSlice, ok := walkDirErrors.([]interface{})
require.True(t, ok, "walkDirErrors should be a slice")
assert.Empty(t, walkDirErrorsSlice, "There should be no walkDir errors")
manager.PrintPluginPoolMetrics(opts.ID)
}
// TestGojaPluginSystemCommands tests the command execution functionality in the system module
func TestGojaPluginSystemCommands(t *testing.T) {
// Create a temporary directory for testing
tempDir := t.TempDir()
// Create a test file to use with commands
testFilePath := filepath.Join(tempDir, "test.txt")
err := os.WriteFile(testFilePath, []byte("Hello, world!"), 0644)
require.NoError(t, err)
payload := `
function init() {
$ui.register((ctx) => {
console.log("Testing command execution");
// Test executing a simple command
try {
// Create a command to list files
const cmd = $os.cmd("ls", "-la", "${TEMP_DIR}");
// Set up stdout capture
const stdoutPipe = cmd.stdoutPipe();
// Start the command
cmd.start();
// Read the output
const output = $io.readAll(stdoutPipe);
console.log("Command output:", $toString(output));
$store.set("commandOutput", $toString(output));
// Wait for the command to complete
cmd.wait();
// Check exit code
const exitCode = cmd.processState.exitCode();
console.log("Command exit code:", exitCode);
$store.set("commandExitCode", exitCode);
} catch (e) {
console.log("Command execution error:", e.message);
$store.set("commandError", e.message);
}
// Test executing an async command
//try {
// // Create a command to list files
// const asyncCmd = $osExtra.asyncCmd("ls", "-la", "${TEMP_DIR}");
//
//
// asyncCmd.run((data, err, exitCode, signal) => {
// // console.log(data, err, exitCode, signal)
// if (data) {
// console.log("Async command data:", $toString(data));
// }
// if (err) {
// console.log("Async command error:", $toString(err));
// }
// if (exitCode) {
// console.log("Async command exit code:", exitCode);
// }
// if (signal) {
// console.log("Async command signal:", signal);
// }
// });
//} catch (e) {
// console.log("Command execution error:", e.message);
// $store.set("asyncCommandError", e.message);
//}
// // Try unsafe goroutine
// try {
// // Create a command to list files
// const cmd = $os.cmd("ls", "-la", "${TEMP_DIR}");
// $store.watch("unsafeGoroutineOutput", (output) => {
// console.log("Unsafe goroutine output:", output);
// });
// // Read the output using scanner
// $unsafeGoroutine(function() {
// // Set up stdout capture
// const stdoutPipe = cmd.stdoutPipe();
// console.log("Starting unsafe goroutine");
// const output = $io.readAll(stdoutPipe);
// $store.set("unsafeGoroutineOutput", $toString(output));
// console.log("Unsafe goroutine output set", $toString(output));
// cmd.wait();
// });
// // Start the command
// cmd.start();
// // Check exit code
// const exitCode = cmd.processState.exitCode();
// console.log("Command exit code:", exitCode);
// } catch (e) {
// console.log("Command execution error:", e.message);
// $store.set("unsafeGoroutineError", e.message);
// }
// Test executing a command with combined output
try {
// Create a command to find a string in a file
const cmd = $os.cmd("grep", "Hello", "${TEST_FILE_PATH}");
// Run the command and capture output
const output = cmd.combinedOutput();
console.log("Grep output:", $toString(output));
$store.set("grepOutput", $toString(output));
// Check if the command found the string
const foundString = $toString(output).includes("Hello");
console.log("Found string:", foundString);
$store.set("foundString", foundString);
} catch (e) {
console.log("Grep execution error:", e.message);
$store.set("grepError", e.message);
}
// Test executing a command with input
try {
// Create a command to sort lines
const cmd = $os.cmd("sort");
// Set up stdin and stdout pipes
const stdinPipe = cmd.stdinPipe();
const stdoutPipe = cmd.stdoutPipe();
// Start the command
cmd.start();
// Write to stdin
$io.writeString(stdinPipe, "c\nb\na\n");
stdinPipe.close();
// Read sorted output
const sortedOutput = $io.readAll(stdoutPipe);
console.log("Sorted output:", $toString(sortedOutput));
$store.set("sortedOutput", $toString(sortedOutput));
// Wait for the command to complete
cmd.wait();
} catch (e) {
console.log("Sort execution error:", e.message);
$store.set("sortError", e.message);
}
// Test unauthorized command
try {
// Try to execute an unauthorized command
const cmd = $os.cmd("open", "https://google.com");
cmd.run();
$store.set("unauthorizedCommandRan", true);
} catch (e) {
console.log("Unauthorized command error:", e.message);
$store.set("unauthorizedCommandError", e.message);
$store.set("unauthorizedCommandRan", false);
}
});
}
`
// Replace placeholders with actual paths
payload = strings.ReplaceAll(payload, "${TEMP_DIR}", tempDir)
payload = strings.ReplaceAll(payload, "${TEST_FILE_PATH}", testFilePath)
opts := DefaultTestPluginOptions()
opts.Payload = payload
opts.Permissions = extension.PluginPermissions{
Scopes: []extension.PluginPermissionScope{
extension.PluginPermissionSystem,
},
Allow: extension.PluginAllowlist{
ReadPaths: []string{tempDir + "/**/*"},
WritePaths: []string{tempDir + "/**/*"},
CommandScopes: []extension.CommandScope{
{
Command: "ls",
Args: []extension.CommandArg{
{Value: "-la"},
{Validator: "$PATH"},
},
},
{
Command: "grep",
Args: []extension.CommandArg{
{Value: "Hello"},
{Validator: "$PATH"},
},
},
{
Command: "sort",
Args: []extension.CommandArg{},
},
},
},
}
fmt.Println(opts.Permissions.GetDescription())
plugin, _, manager, _, _, err := InitTestPlugin(t, opts)
require.NoError(t, err)
// Wait for the plugin to execute
time.Sleep(1 * time.Second)
// Check the store values for the ls command
commandOutput, ok := plugin.store.GetOk("commandOutput")
require.True(t, ok, "commandOutput should be set in store")
assert.Contains(t, commandOutput.(string), "test.txt", "Command output should contain the test file")
commandExitCode, ok := plugin.store.GetOk("commandExitCode")
require.True(t, ok, "commandExitCode should be set in store")
assert.Equal(t, int64(0), commandExitCode, "Command exit code should be 0")
// Check the store values for the grep command
grepOutput, ok := plugin.store.GetOk("grepOutput")
if ok {
assert.Contains(t, grepOutput.(string), "Hello", "Grep output should contain 'Hello'")
}
foundString, ok := plugin.store.GetOk("foundString")
if ok {
assert.True(t, foundString.(bool), "Should have found the string in the file")
}
// Check the store values for the sort command
sortedOutput, ok := plugin.store.GetOk("sortedOutput")
if ok {
// Expected output: "a\nb\nc\n" (sorted)
assert.Contains(t, sortedOutput.(string), "a", "Sorted output should contain 'a'")
assert.Contains(t, sortedOutput.(string), "b", "Sorted output should contain 'b'")
assert.Contains(t, sortedOutput.(string), "c", "Sorted output should contain 'c'")
// Check if the lines are in the correct order
lines := strings.Split(strings.TrimSpace(sortedOutput.(string)), "\n")
if len(lines) >= 3 {
assert.Equal(t, "a", lines[0], "First line should be 'a'")
assert.Equal(t, "b", lines[1], "Second line should be 'b'")
assert.Equal(t, "c", lines[2], "Third line should be 'c'")
}
}
// Check that unauthorized command was rejected
unauthorizedCommandRan, ok := plugin.store.GetOk("unauthorizedCommandRan")
require.True(t, ok, "unauthorizedCommandRan should be set in store")
assert.False(t, unauthorizedCommandRan.(bool), "Unauthorized command should not have run")
unauthorizedCommandError, ok := plugin.store.GetOk("unauthorizedCommandError")
require.True(t, ok, "unauthorizedCommandError should be set in store")
assert.Contains(t, unauthorizedCommandError.(string), "not authorized", "Error should indicate command was not authorized")
manager.PrintPluginPoolMetrics(opts.ID)
}
func TestGojaPluginSystemAsyncCommand(t *testing.T) {
// Create a temporary directory for testing
tempDir := t.TempDir()
// Create a test file to use with commands
testFilePath := filepath.Join(tempDir, "test.txt")
err := os.WriteFile(testFilePath, []byte("Hello, world!"), 0644)
require.NoError(t, err)
payload := `
function init() {
$ui.register((ctx) => {
console.log("Testing async command execution");
// Test executing an async command
try {
// Create a command to list files
let asyncCmd = $osExtra.asyncCmd("ls", "-la", "${TEMP_DIR}");
let output = "";
asyncCmd.run((data, err, exitCode, signal) => {
// console.log(data, err, exitCode, signal)
if (data) {
// console.log("Async command data:", $toString(data));
output += $toString(data) + "\n";
$store.set("asyncCommandData", $toString(output));
}
if (err) {
console.log("Async command error:", $toString(err));
$store.set("asyncCommandError", $toString(err));
}
if (exitCode !== undefined) {
console.log("output 1", output)
console.log("Async command exit code:", exitCode);
$store.set("asyncCommandExitCode", exitCode);
console.log("Async command signal:", signal);
$store.set("asyncCommandSignal", signal);
}
});
console.log("Running second command")
let asyncCmd2 = $osExtra.asyncCmd("ls", "-la", "${TEMP_DIR}");
let output2 = "";
asyncCmd2.run((data, err, exitCode, signal) => {
// console.log(data, err, exitCode, signal)
if (data) {
// console.log("Async command data:", $toString(data));
output2 += $toString(data) + "\n";
$store.set("asyncCommandData", $toString(output2));
}
if (err) {
console.log("Async command error:", $toString(err));
$store.set("asyncCommandError", $toString(err));
}
if (exitCode !== undefined) {
console.log("output 2", output2)
console.log("Async command exit code:", exitCode);
$store.set("asyncCommandExitCode", exitCode);
console.log("Async command signal:", signal);
$store.set("asyncCommandSignal", signal);
}
});
} catch (e) {
console.log("Command execution error:", e.message);
$store.set("asyncCommandError", e.message);
}
});
}
`
// Replace placeholders with actual paths
payload = strings.ReplaceAll(payload, "${TEMP_DIR}", tempDir)
payload = strings.ReplaceAll(payload, "${TEST_FILE_PATH}", testFilePath)
opts := DefaultTestPluginOptions()
opts.Payload = payload
opts.Permissions = extension.PluginPermissions{
Scopes: []extension.PluginPermissionScope{
extension.PluginPermissionSystem,
},
Allow: extension.PluginAllowlist{
ReadPaths: []string{tempDir + "/**/*"},
WritePaths: []string{tempDir + "/**/*"},
CommandScopes: []extension.CommandScope{
{
Command: "ls",
Args: []extension.CommandArg{
{Value: "-la"},
{Validator: "$PATH"},
},
},
},
},
}
plugin, _, manager, _, _, err := InitTestPlugin(t, opts)
require.NoError(t, err)
// Wait for the plugin to execute
time.Sleep(2 * time.Second)
// Check the store values for the ls command
asyncCommandData, ok := plugin.store.GetOk("asyncCommandData")
require.True(t, ok, "asyncCommandData should be set in store")
assert.Contains(t, asyncCommandData.(string), "test.txt", "Command output should contain the test file")
asyncCommandExitCode, ok := plugin.store.GetOk("asyncCommandExitCode")
require.True(t, ok, "asyncCommandExitCode should be set in store")
assert.Equal(t, int64(0), asyncCommandExitCode, "Command exit code should be 0")
manager.PrintPluginPoolMetrics(opts.ID)
}
// TestGojaPluginSystemUnzip tests the unzip functionality in the osExtra module
func TestGojaPluginSystemUnzip(t *testing.T) {
// Create a temporary directory for testing
tempDir := t.TempDir()
// Create a test zip file
zipPath := filepath.Join(tempDir, "test.zip")
extractPath := filepath.Join(tempDir, "extracted")
// Create a zip file with test content
err := createTestZipFile(zipPath)
require.NoError(t, err, "Failed to create test zip file")
payload := `
function init() {
$ui.register((ctx) => {
console.log("Testing $osExtra unzip functionality");
try {
// Create extraction directory
$os.mkdirAll("${EXTRACT_PATH}", 0755);
// Unzip the file
$osExtra.unzip("${ZIP_PATH}", "${EXTRACT_PATH}");
console.log("Unzip successful");
$store.set("unzipSuccess", true);
// Check if files were extracted
const entries = $os.readDir("${EXTRACT_PATH}");
const fileNames = entries.map(entry => entry.name());
console.log("Extracted files:", fileNames);
$store.set("extractedFiles", fileNames);
// Read content of extracted file
const content = $os.readFile("${EXTRACT_PATH}/test.txt");
console.log("Extracted content:", $toString(content));
$store.set("extractedContent", $toString(content));
// Try to unzip to an unauthorized location
try {
$osExtra.unzip("${ZIP_PATH}", "/tmp/unauthorized");
$store.set("unauthorizedUnzipSuccess", true);
} catch (e) {
console.log("Unauthorized unzip error:", e.message);
$store.set("unauthorizedUnzipError", e.message);
$store.set("unauthorizedUnzipSuccess", false);
}
} catch (e) {
console.log("Unzip error:", e.message);
$store.set("unzipError", e.message);
}
});
}
`
// Replace placeholders with actual paths
payload = strings.ReplaceAll(payload, "${ZIP_PATH}", zipPath)
payload = strings.ReplaceAll(payload, "${EXTRACT_PATH}", extractPath)
opts := DefaultTestPluginOptions()
opts.Payload = payload
opts.Permissions = extension.PluginPermissions{
Scopes: []extension.PluginPermissionScope{
extension.PluginPermissionSystem,
},
Allow: extension.PluginAllowlist{
ReadPaths: []string{tempDir + "/**/*"},
WritePaths: []string{tempDir + "/**/*"},
},
}
plugin, _, manager, _, _, err := InitTestPlugin(t, opts)
require.NoError(t, err)
// Wait for the plugin to execute
time.Sleep(1 * time.Second)
// Check the store values
unzipSuccess, ok := plugin.store.GetOk("unzipSuccess")
require.True(t, ok, "unzipSuccess should be set in store")
assert.True(t, unzipSuccess.(bool), "Unzip should have succeeded")
extractedFiles, ok := plugin.store.GetOk("extractedFiles")
require.True(t, ok, "extractedFiles should be set in store")
filesSlice, ok := extractedFiles.([]interface{})
require.True(t, ok, "extractedFiles should be a slice")
assert.Contains(t, filesSlice, "test.txt", "Extracted files should include test.txt")
extractedContent, ok := plugin.store.GetOk("extractedContent")
require.True(t, ok, "extractedContent should be set in store")
assert.Equal(t, "Test content for zip file", extractedContent, "Extracted content should match original")
// Check unauthorized unzip attempt
unauthorizedUnzipSuccess, ok := plugin.store.GetOk("unauthorizedUnzipSuccess")
require.True(t, ok, "unauthorizedUnzipSuccess should be set in store")
assert.False(t, unauthorizedUnzipSuccess.(bool), "Unauthorized unzip should have failed")
unauthorizedUnzipError, ok := plugin.store.GetOk("unauthorizedUnzipError")
require.True(t, ok, "unauthorizedUnzipError should be set in store")
assert.Contains(t, unauthorizedUnzipError.(string), "not authorized", "Error should indicate path not authorized")
manager.PrintPluginPoolMetrics(opts.ID)
}
// TestGojaPluginSystemMime tests the mime module functionality
func TestGojaPluginSystemMime(t *testing.T) {
payload := `
function init() {
$ui.register((ctx) => {
console.log("Testing $mime functionality");
try {
// Test parsing content type
const contentType = "text/html; charset=utf-8";
const parsed = $mime.parse(contentType);
console.log("Parsed content type:", parsed);
$store.set("parsedContentType", parsed);
// Test parsing content type with multiple parameters
const contentTypeWithParams = "application/json; charset=utf-8; boundary=something";
const parsedWithParams = $mime.parse(contentTypeWithParams);
console.log("Parsed content type with params:", parsedWithParams);
$store.set("parsedContentTypeWithParams", parsedWithParams);
// Test formatting content type
const formatted = $mime.format("text/plain", { charset: "utf-8", boundary: "boundary" });
console.log("Formatted content type:", formatted);
$store.set("formattedContentType", formatted);
// Test parsing invalid content type
try {
const invalidContentType = "invalid content type";
const parsedInvalid = $mime.parse(invalidContentType);
console.log("Parsed invalid content type:", parsedInvalid);
$store.set("parsedInvalidContentType", parsedInvalid);
} catch (e) {
console.log("Invalid content type error:", e.message);
$store.set("invalidContentTypeError", e.message);
}
} catch (e) {
console.log("Mime test error:", e.message);
$store.set("mimeTestError", e.message);
}
});
}
`
opts := DefaultTestPluginOptions()
opts.Payload = payload
opts.Permissions = extension.PluginPermissions{
Scopes: []extension.PluginPermissionScope{
extension.PluginPermissionSystem,
},
}
plugin, _, manager, _, _, err := InitTestPlugin(t, opts)
require.NoError(t, err)
// Wait for the plugin to execute
time.Sleep(1 * time.Second)
// Check the store values for parsed content type
parsedContentType, ok := plugin.store.GetOk("parsedContentType")
require.True(t, ok, "parsedContentType should be set in store")
parsedMap, ok := parsedContentType.(map[string]interface{})
require.True(t, ok, "parsedContentType should be a map")
// Check media type
mediaType, ok := parsedMap["mediaType"]
require.True(t, ok, "mediaType should be in parsed result")
assert.Equal(t, "text/html", mediaType, "Media type should be text/html")
// Check parameters
parameters, ok := parsedMap["parameters"]
require.True(t, ok, "parameters should be in parsed result")
paramsMap, ok := parameters.(map[string]string)
require.Truef(t, ok, "parameters should be a map but got %T", parameters)
assert.Equal(t, "utf-8", paramsMap["charset"], "charset parameter should be utf-8")
// Check parsed content type with multiple parameters
parsedWithParams, ok := plugin.store.GetOk("parsedContentTypeWithParams")
require.True(t, ok, "parsedContentTypeWithParams should be set in store")
parsedWithParamsMap, ok := parsedWithParams.(map[string]interface{})
require.Truef(t, ok, "parsedContentTypeWithParams should be a map but got %T", parsedWithParams)
// Check media type
mediaTypeWithParams, ok := parsedWithParamsMap["mediaType"]
require.True(t, ok, "mediaType should be in parsed result")
assert.Equal(t, "application/json", mediaTypeWithParams, "Media type should be application/json")
// Check parameters
parametersWithParams, ok := parsedWithParamsMap["parameters"]
require.True(t, ok, "parameters should be in parsed result")
require.Truef(t, ok, "parameters should be a map but got %T", parametersWithParams)
// Check formatted content type
formattedContentType, ok := plugin.store.GetOk("formattedContentType")
require.True(t, ok, "formattedContentType should be set in store")
assert.Contains(t, formattedContentType.(string), "text/plain", "Formatted content type should contain text/plain")
assert.Contains(t, formattedContentType.(string), "charset=utf-8", "Formatted content type should contain charset=utf-8")
assert.Contains(t, formattedContentType.(string), "boundary=boundary", "Formatted content type should contain boundary=boundary")
// Check invalid content type error
invalidContentTypeError, ok := plugin.store.GetOk("invalidContentTypeError")
if ok {
assert.NotEmpty(t, invalidContentTypeError, "Invalid content type should have produced an error")
}
manager.PrintPluginPoolMetrics(opts.ID)
}
// Helper function to create a test zip file
func createTestZipFile(zipPath string) error {
// Create a buffer to write our zip to
zipFile, err := os.Create(zipPath)
if err != nil {
return err
}
defer zipFile.Close()
// Create a new zip archive
zipWriter := zip.NewWriter(zipFile)
defer zipWriter.Close()
// Add a file to the archive
fileWriter, err := zipWriter.Create("test.txt")
if err != nil {
return err
}
// Write content to the file
_, err = fileWriter.Write([]byte("Test content for zip file"))
if err != nil {
return err
}
// Add a directory to the archive
_, err = zipWriter.Create("testdir/")
if err != nil {
return err
}
// Add a file in the directory
dirFileWriter, err := zipWriter.Create("testdir/nested.txt")
if err != nil {
return err
}
// Write content to the nested file
_, err = dirFileWriter.Write([]byte("Nested file content"))
if err != nil {
return err
}
return nil
}