Add runtime SQLite query targets
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"browser.local/run/protocol"
|
||||
)
|
||||
|
||||
func TestSQLiteQueryReturnsDeclaredRowsVerbatim(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
assignment := sqliteQueryAssignment()
|
||||
scope, err := NewWorkspaceResolver(root).Scope(assignment.ServerInstanceID, assignment.ExecutionInput.WorkspaceScope)
|
||||
if err != nil {
|
||||
t.Fatalf("create scope: %v", err)
|
||||
}
|
||||
createSQLiteQueryFixture(t, filepath.Join(scope, assignment.TargetKey))
|
||||
writeSQLiteQueryAsset(t, scope, assignment.ExecutionInput.Inputs["sqlRef"], `
|
||||
SELECT id AS steamId, name AS displayName, note AS originalText
|
||||
FROM users
|
||||
WHERE (:steamId IS NULL OR id = :steamId)
|
||||
ORDER BY id
|
||||
LIMIT :limit;
|
||||
`)
|
||||
|
||||
result := NewSQLiteQueryExecutor(root).Execute(context.Background(), assignment)
|
||||
if result.State != lifecycleResultStateSucceeded || result.ExecutionResult.Kind != "sqlite.query" || result.ExecutionResult.Checksum == "" || result.ExecutionResult.SizeBytes <= 0 {
|
||||
t.Fatalf("expected successful SQLite rows, got %+v", result)
|
||||
}
|
||||
var payload struct {
|
||||
Rows []map[string]any `json:"rows"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(result.ExecutionResult.Content), &payload); err != nil {
|
||||
t.Fatalf("decode rows: %v", err)
|
||||
}
|
||||
if len(payload.Rows) != 2 || payload.Rows[0]["steamId"] != "steam-1" || payload.Rows[0]["originalText"] != "password=opaque C:/game/users.db" {
|
||||
t.Fatalf("SQLite rows were not faithfully returned: %+v", payload.Rows)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSQLiteQueryRejectsUnsafeAssetsAndLimitOverrun(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
assignment := sqliteQueryAssignment()
|
||||
scope, err := NewWorkspaceResolver(root).Scope(assignment.ServerInstanceID, assignment.ExecutionInput.WorkspaceScope)
|
||||
if err != nil {
|
||||
t.Fatalf("create scope: %v", err)
|
||||
}
|
||||
createSQLiteQueryFixture(t, filepath.Join(scope, assignment.TargetKey))
|
||||
writeSQLiteQueryAsset(t, scope, assignment.ExecutionInput.Inputs["sqlRef"], "SELECT id AS steamId FROM users ORDER BY id")
|
||||
|
||||
unsafe := assignment
|
||||
unsafe.ExecutionInput.Inputs = map[string]string{"sqlRef": "../outside.sql", "maxRows": "2"}
|
||||
if result := NewSQLiteQueryExecutor(root).Execute(context.Background(), unsafe); result.ErrorCode != "invalid_request" {
|
||||
t.Fatalf("expected unsafe asset rejection, got %+v", result)
|
||||
}
|
||||
limited := assignment
|
||||
limited.ExecutionInput.Inputs = map[string]string{"sqlRef": assignment.ExecutionInput.Inputs["sqlRef"], "maxRows": "1"}
|
||||
if result := NewSQLiteQueryExecutor(root).Execute(context.Background(), limited); result.ErrorCode != "result_limit_exceeded" {
|
||||
t.Fatalf("expected result limit failure instead of truncation, got %+v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func sqliteQueryAssignment() protocol.RunJobAssignment {
|
||||
assignment := lifecycleAssignment(protocol.RunCapabilityRemoteRunDBSQLiteQuery)
|
||||
assignment.TargetKey = "databases/current.db"
|
||||
assignment.InputRef = "input://plugin-query-poll/server-1/users"
|
||||
assignment.ExecutionInput.WorkspaceScope = "profile-default"
|
||||
assignment.ExecutionInput.RemoteAdapterKey = "current-db"
|
||||
assignment.ExecutionInput.RemoteAdapterKind = "database"
|
||||
assignment.ExecutionInput.TimeoutSeconds = 10
|
||||
assignment.ExecutionInput.Inputs = map[string]string{"sqlRef": "sql/users.sql", "maxRows": "2"}
|
||||
return assignment
|
||||
}
|
||||
|
||||
func createSQLiteQueryFixture(t *testing.T, path string) {
|
||||
t.Helper()
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
database, err := sql.Open(sqliteSchemaProbeDriver, path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer database.Close()
|
||||
if _, err := database.Exec(`CREATE TABLE users (id TEXT PRIMARY KEY, name TEXT NOT NULL, note TEXT); INSERT INTO users(id, name, note) VALUES ('steam-1', 'Ada', 'password=opaque C:/game/users.db'), ('steam-2', 'Lin', 'unchanged');`); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func writeSQLiteQueryAsset(t *testing.T, scope string, asset string, body string) {
|
||||
t.Helper()
|
||||
path := filepath.Join(scope, filepath.FromSlash(asset))
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(path, []byte(body), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user