mirror of
https://github.com/phishingclub/phishingclub.git
synced 2026-10-03 22:16:49 +02:00
103 lines
3.1 KiB
Go
103 lines
3.1 KiB
Go
package script
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// TestEntry is one line in the run log. It mirrors the remote browser editor's
|
|
// runLog entry shape so the editor renders both the same way.
|
|
// - type "log": Message (+ optional Data)
|
|
// - type "info": Message
|
|
// - type "event": Key (event name) + Value (event data) — an emitEvent call
|
|
// - type "error": Message
|
|
// - type "done": end marker
|
|
type TestEntry struct {
|
|
Type string `json:"type"`
|
|
Time string `json:"time"`
|
|
Message string `json:"message,omitempty"`
|
|
Data interface{} `json:"data,omitempty"`
|
|
Key string `json:"key,omitempty"`
|
|
Value interface{} `json:"value,omitempty"`
|
|
}
|
|
|
|
// TestResult is the captured output of a test run: an ordered run log plus the
|
|
// terminal error, if any.
|
|
type TestResult struct {
|
|
OK bool `json:"ok"`
|
|
DurationMs int64 `json:"durationMs"`
|
|
Entries []TestEntry `json:"entries"`
|
|
Error string `json:"error,omitempty"`
|
|
ErrorPhase string `json:"errorPhase,omitempty"`
|
|
|
|
mu sync.Mutex
|
|
}
|
|
|
|
// maxTestEntries caps the captured run log so a script that logs in a tight loop
|
|
// cannot grow the response unboundedly and exhaust memory in the request handler.
|
|
const maxTestEntries = 2000
|
|
|
|
func (t *TestResult) add(e TestEntry) {
|
|
e.Time = time.Now().UTC().Format(time.RFC3339Nano)
|
|
t.mu.Lock()
|
|
defer t.mu.Unlock()
|
|
if len(t.Entries) >= maxTestEntries {
|
|
if len(t.Entries) == maxTestEntries {
|
|
t.Entries = append(t.Entries, TestEntry{
|
|
Type: "log",
|
|
Time: e.Time,
|
|
Message: "… output truncated (too many entries)",
|
|
})
|
|
}
|
|
return
|
|
}
|
|
t.Entries = append(t.Entries, e)
|
|
}
|
|
|
|
func (t *TestResult) addLog(msg string, data interface{}) {
|
|
t.add(TestEntry{Type: "log", Message: msg, Data: data})
|
|
}
|
|
|
|
func (t *TestResult) addInfo(msg string, data interface{}) {
|
|
t.add(TestEntry{Type: "info", Message: msg, Data: data})
|
|
}
|
|
|
|
func (t *TestResult) addEvent(name string, data map[string]interface{}) {
|
|
t.add(TestEntry{Type: "event", Key: name, Value: data})
|
|
}
|
|
|
|
func (t *TestResult) addFetchOK(method, url string, status int) {
|
|
t.add(TestEntry{Type: "log", Message: fmt.Sprintf("%s %s → %d", method, url, status)})
|
|
}
|
|
|
|
func (t *TestResult) addFetchErr(method, url, errMsg string) {
|
|
t.add(TestEntry{Type: "log", Message: fmt.Sprintf("%s %s ✗ %s", method, url, errMsg)})
|
|
}
|
|
|
|
func (t *TestResult) setError(phase, msg string) {
|
|
t.mu.Lock()
|
|
if t.Error == "" {
|
|
t.Error = msg
|
|
t.ErrorPhase = phase
|
|
}
|
|
t.mu.Unlock()
|
|
t.add(TestEntry{Type: "error", Message: msg})
|
|
}
|
|
|
|
// RunTest runs a script against a simulated event and captures what it does as
|
|
// an ordered run log, without touching the campaign: log, info and emitEvent are
|
|
// recorded rather than applied, and no webhooks fire. http.fetch runs for real.
|
|
// The Runner's Timeout bounds the run.
|
|
func (r *Runner) RunTest(script string, event EventContext) *TestResult {
|
|
res := &TestResult{Entries: []TestEntry{}}
|
|
start := time.Now()
|
|
r.run(Job{ScriptID: "test", Script: script, Event: event, test: res})
|
|
res.DurationMs = time.Since(start).Milliseconds()
|
|
res.OK = res.Error == ""
|
|
if res.OK {
|
|
res.add(TestEntry{Type: "done"})
|
|
}
|
|
return res
|
|
}
|