test: close remaining workspace preprocessing gates
This commit is contained in:
@@ -3,6 +3,7 @@ package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
@@ -22,6 +23,11 @@ import (
|
||||
"github.com/aritmolab/thothii/tools/thothctl/internal/workspaceops"
|
||||
)
|
||||
|
||||
const (
|
||||
maxPublicStdoutBytes = 1 << 20
|
||||
maxPublicStderrBytes = 64 << 10
|
||||
)
|
||||
|
||||
const usage = `Usage: thothctl --installation <absolute-path>/thothii-installation.yaml <command>
|
||||
|
||||
Commands:
|
||||
@@ -62,6 +68,10 @@ func main() {
|
||||
}
|
||||
|
||||
func run(ctx context.Context, args []string, stdout, stderr io.Writer) int {
|
||||
// Apply the public stream bounds after all sanitization and final encoding.
|
||||
// In particular, JSON escaping can expand an otherwise accepted child result.
|
||||
stdout = &boundedWriter{dst: stdout, maximum: maxPublicStdoutBytes}
|
||||
stderr = &boundedWriter{dst: stderr, maximum: maxPublicStderrBytes}
|
||||
if len(args) == 1 && (args[0] == "--help" || args[0] == "-h") {
|
||||
fmt.Fprint(stdout, usage)
|
||||
return 0
|
||||
@@ -119,7 +129,8 @@ func run(ctx context.Context, args []string, stdout, stderr io.Writer) int {
|
||||
jsonMode = c.JSON
|
||||
}
|
||||
if jsonMode {
|
||||
if err := json.NewEncoder(stdout).Encode(result); err != nil {
|
||||
if err := writeWorkspaceJSON(stdout, result); err != nil {
|
||||
fmt.Fprintln(stderr, "thothctl: workspace result exceeds output limit")
|
||||
return 1
|
||||
}
|
||||
} else {
|
||||
@@ -221,6 +232,45 @@ func workspaceUsageError(err error) bool {
|
||||
strings.Contains(message, "SQL input exceeds") || strings.Contains(message, "annotation input exceeds")
|
||||
}
|
||||
|
||||
type boundedWriter struct {
|
||||
dst io.Writer
|
||||
maximum int64
|
||||
written int64
|
||||
}
|
||||
|
||||
func (w *boundedWriter) Write(p []byte) (int, error) {
|
||||
remaining := w.maximum - w.written
|
||||
if remaining <= 0 {
|
||||
return 0, io.ErrShortWrite
|
||||
}
|
||||
if int64(len(p)) > remaining {
|
||||
n, err := w.dst.Write(p[:int(remaining)])
|
||||
w.written += int64(n)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
return n, io.ErrShortWrite
|
||||
}
|
||||
n, err := w.dst.Write(p)
|
||||
w.written += int64(n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func writeWorkspaceJSON(w io.Writer, result workspaceops.Result) error {
|
||||
var encoded bytes.Buffer
|
||||
encoder := json.NewEncoder(&encoded)
|
||||
// Keep public output compact and avoid HTML-escape amplification of warnings.
|
||||
encoder.SetEscapeHTML(false)
|
||||
if err := encoder.Encode(result); err != nil {
|
||||
return err
|
||||
}
|
||||
if encoded.Len() > maxPublicStdoutBytes {
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
_, err := w.Write(encoded.Bytes())
|
||||
return err
|
||||
}
|
||||
|
||||
func renderWorkspaceHuman(w io.Writer, result workspaceops.Result) {
|
||||
if result.Code == workspaceops.CodeRegistryBootstrapRecoveryConflict {
|
||||
fmt.Fprintln(w, "Bootstrap recovery is ambiguous or corrupt; inspect the installation registry jobs.")
|
||||
|
||||
@@ -132,6 +132,42 @@ func TestRunWorkspacePublicDispatchExitMatrix(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunWorkspaceBoundsFinalJSONEncoding(t *testing.T) {
|
||||
fixture := newCLIFixture(t, "")
|
||||
fixture.setEnvironment(t)
|
||||
result := fmt.Sprintf(`{"schemaVersion":1,"status":"succeeded","code":"ok","workspaceId":"psd","workspaceRevision":"%s","descriptorBlob":"%s","operation":"inspect","completedStages":[],"warnings":[%q]}`, strings.Repeat("0", 40), strings.Repeat("a", 40), strings.Repeat(`"`, 524000))
|
||||
t.Setenv("THOTHCTL_FAKE_WORKSPACE_RESULT", result)
|
||||
var stdout, stderr bytes.Buffer
|
||||
if code := run(context.Background(), []string{"--installation", fixture.installationPath, "workspace", "inspect", "--workspace", "psd", "--json"}, &stdout, &stderr); code != 1 {
|
||||
t.Fatalf("exit = %d, want operational failure 1", code)
|
||||
}
|
||||
if stdout.Len() > 1<<20 {
|
||||
t.Fatalf("stdout length = %d, exceeds 1 MiB", stdout.Len())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunWorkspaceBoundsFinalHumanEncoding(t *testing.T) {
|
||||
fixture := newCLIFixture(t, "")
|
||||
fixture.setEnvironment(t)
|
||||
result := fmt.Sprintf(`{"schemaVersion":1,"status":"succeeded","code":"ok","workspaceId":"psd","workspaceRevision":"%s","descriptorBlob":"%s","operation":"inspect","completedStages":[%q]}`, strings.Repeat("0", 40), strings.Repeat("a", 40), strings.Repeat("x", (1<<20)-100))
|
||||
t.Setenv("THOTHCTL_FAKE_WORKSPACE_RESULT", result)
|
||||
var stdout, stderr bytes.Buffer
|
||||
_ = run(context.Background(), []string{"--installation", fixture.installationPath, "workspace", "inspect", "--workspace", "psd"}, &stdout, &stderr)
|
||||
if stdout.Len() > 1<<20 {
|
||||
t.Fatalf("stdout length = %d, exceeds 1 MiB", stdout.Len())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunBoundsParseErrorStderr(t *testing.T) {
|
||||
fixture := newCLIFixture(t, "")
|
||||
fixture.setEnvironment(t)
|
||||
var stdout, stderr bytes.Buffer
|
||||
_ = run(context.Background(), []string{"--installation", fixture.installationPath, "workspace", "inspect", "--workspace", "psd", "--unknown", strings.Repeat("x", 70<<10)}, &stdout, &stderr)
|
||||
if stderr.Len() > 64<<10 {
|
||||
t.Fatalf("stderr length = %d, exceeds 64 KiB", stderr.Len())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunWorkspaceOperationalFailureExitsOne(t *testing.T) {
|
||||
fixture := newCLIFixture(t, "")
|
||||
fixture.setEnvironment(t)
|
||||
|
||||
Reference in New Issue
Block a user