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) } }