Class: Profiler::Api::TestRunnerController

Inherits:
Profiler::ApplicationController show all
Includes:
ActionController::Live
Defined in:
app/controllers/profiler/api/test_runner_controller.rb

Instance Method Summary collapse

Instance Method Details

#createObject



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'app/controllers/profiler/api/test_runner_controller.rb', line 24

def create
  files     = Array(params[:files])
  framework = params[:framework] || detect_framework

  if files.empty?
    return render json: { error: "No files selected" }, status: :unprocessable_entity
  end

  # Validate paths are within Rails root (prevent path traversal)
  root = defined?(Rails) ? Rails.root.to_s : Dir.pwd
  files.each do |f|
    expanded = File.expand_path(File.join(root, f))
    unless expanded.start_with?(root)
      return render json: { error: "Invalid file path: #{f}" }, status: :unprocessable_entity
    end
  end

  run = Profiler::TestRunner::Runner.start(files: files, framework: framework)
  render json: run.to_h, status: :created
end

#destroyObject



95
96
97
98
99
100
101
102
# File 'app/controllers/profiler/api/test_runner_controller.rb', line 95

def destroy
  killed = Profiler::TestRunner::Runner.kill(params[:id])
  if killed
    head :no_content
  else
    render json: { error: "Run not found or not running" }, status: :not_found
  end
end

#filesObject



13
14
15
16
17
18
19
20
21
22
# File 'app/controllers/profiler/api/test_runner_controller.rb', line 13

def files
  framework = params[:framework]
  tree = Profiler::TestRunner::Discovery.files(framework: framework)
  frameworks = Profiler::TestRunner::Discovery.frameworks

  render json: {
    frameworks: frameworks,
    tree: tree
  }
end

#showObject



45
46
47
48
49
50
# File 'app/controllers/profiler/api/test_runner_controller.rb', line 45

def show
  run = Profiler::TestRunner.run_store.find(params[:id])
  return render json: { error: "Run not found" }, status: :not_found unless run

  render json: run.to_h
end

#streamObject

SSE endpoint — streams output chunks as server-sent events. Replaces polling for live test output in the frontend.



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'app/controllers/profiler/api/test_runner_controller.rb', line 54

def stream
  run = Profiler::TestRunner.run_store.find(params[:id])
  unless run
    render json: { error: "Run not found" }, status: :not_found
    return
  end

  response.headers["Content-Type"]  = "text/event-stream"
  response.headers["Cache-Control"] = "no-cache"
  response.headers["X-Accel-Buffering"] = "no"

  sse = SSE.new(response.stream, retry: 1000, event: "output")
  position = 0

  begin
    loop do
      result = Profiler::TestRunner.run_store.wait_for_output(
        params[:id], position: position, timeout: 15
      )

      result[:chunks].each do |chunk|
        sse.write({ chunk: chunk })
      end
      position = result[:position]

      if result[:finished]
        current_run = Profiler::TestRunner.run_store.find(params[:id])
        sse.write(
          { status: result[:status], exit_code: current_run&.exit_code },
          event: "done"
        )
        break
      end
    end
  rescue ActionController::Live::ClientDisconnected, IOError
    # Client navigated away — normal exit
  ensure
    sse.close
  end
end