Cybersecurity-Projects/PROJECTS/beginner/steganography-multi-tool/internal/tui/flows_test.go

225 lines
5.9 KiB
Go

/*
©AngelaMos | 2026
flows_test.go
Coverage for the file-mode payload, save flow, reset, and command plumbing
*/
package tui
import (
"os"
"path/filepath"
"strings"
"testing"
)
type nopMsg struct{}
func hideStego(t *testing.T, secret string) []byte {
t.Helper()
out := filepath.Join(t.TempDir(), "s.png")
cover := makePNG(t, 64, 64)
m := ready()
m, _ = step(m, keyEnter())
m = pickFormat(t, m, "image")
m, _ = step(m, keyEnter())
m, _ = step(m, fileLoadedMsg{origin: stageCover, path: "c.png", data: cover})
m, _ = step(m, typeText(secret))
m, _ = step(m, keyEnter())
m, _ = step(m, keyEnter())
m.outPath.SetValue(out)
m, _ = step(m, keyEnter())
m, cmd := step(m, keyEnter())
m = finishRun(t, m, cmd)
if m.engineErr != nil {
t.Fatalf("hide errored: %v", m.engineErr)
}
data, err := os.ReadFile(out)
if err != nil {
t.Fatalf("read stego: %v", err)
}
return data
}
func TestInitReturnsCmd(t *testing.T) {
if New().Init() == nil {
t.Fatalf("Init returned nil")
}
}
func TestLoadFileCmd(t *testing.T) {
p := filepath.Join(t.TempDir(), "f")
if err := os.WriteFile(p, []byte("hi"), 0o600); err != nil {
t.Fatalf("write: %v", err)
}
msg, ok := loadFileCmd(stageCover, p)().(fileLoadedMsg)
if !ok || msg.err != nil || string(msg.data) != "hi" || msg.origin != stageCover {
t.Fatalf("loadFileCmd = %+v ok=%v", msg, ok)
}
miss, _ := loadFileCmd(stageCover, filepath.Join(t.TempDir(), "nope"))().(fileLoadedMsg)
if miss.err == nil {
t.Fatalf("expected an error for a missing file")
}
}
func TestPayloadFileMode(t *testing.T) {
cover := makePNG(t, 48, 48)
m := ready()
m, _ = step(m, keyEnter())
m = pickFormat(t, m, "image")
m, _ = step(m, keyEnter())
m, _ = step(m, fileLoadedMsg{origin: stageCover, path: "c.png", data: cover})
if m.stage != stagePayload {
t.Fatalf("stage = %v", m.stage)
}
m, _ = step(m, keyTab())
if m.payloadMode != payloadFile {
t.Fatalf("tab did not switch to file mode")
}
m, _ = step(m, fileLoadedMsg{origin: stagePayload, path: "/x/secret.bin", data: []byte("payloaddata")})
if m.stage != stageSecure {
t.Fatalf("after payload file: stage = %v", m.stage)
}
if m.payloadLabel != "secret.bin" {
t.Fatalf("payload label = %q", m.payloadLabel)
}
if string(m.payloadBytes) != "payloaddata" {
t.Fatalf("payload bytes = %q", m.payloadBytes)
}
}
func TestRevealSaveFlow(t *testing.T) {
stego := hideStego(t, "save me")
saveTo := filepath.Join(t.TempDir(), "recovered.txt")
r := ready()
r, _ = step(r, keyDown())
r, _ = step(r, keyEnter())
r, _ = step(r, keyEnter())
r, cmd := step(r, fileLoadedMsg{origin: stageCover, path: "s.png", data: stego})
r = finishRun(t, r, cmd)
if r.stage != stageResult || r.engineErr != nil {
t.Fatalf("reveal: stage=%v err=%v", r.stage, r.engineErr)
}
if !r.canSaveReveal() {
t.Fatalf("revealed payload should be saveable")
}
r, _ = step(r, typeText("s"))
if !r.saving {
t.Fatalf("s did not enter saving mode")
}
r.outPath.SetValue(saveTo)
r, cmd = step(r, keyEnter())
for _, msg := range drain(cmd) {
r, _ = step(r, msg)
}
if r.savedAt != saveTo {
t.Fatalf("savedAt = %q, want %q", r.savedAt, saveTo)
}
got, err := os.ReadFile(saveTo)
if err != nil || string(got) != "save me" {
t.Fatalf("saved file = %q err=%v", got, err)
}
}
func TestResetOnNewRun(t *testing.T) {
m := ready()
m.stage = stageResult
m, _ = step(m, typeText("n"))
if m.stage != stageOperation {
t.Fatalf("new run did not reset, stage = %v", m.stage)
}
}
func TestCoverKeyForwarding(t *testing.T) {
m := ready()
m, _ = step(m, keyEnter())
m = pickFormat(t, m, "image")
m, _ = step(m, keyEnter())
if m.stage != stageCover {
t.Fatalf("stage = %v", m.stage)
}
browse, _ := step(m, keyDown())
if browse.stage != stageCover {
t.Fatalf("browsing changed the stage to %v", browse.stage)
}
back, _ := step(m, keyEsc())
if back.stage != stageFormat {
t.Fatalf("esc from cover went to %v", back.stage)
}
}
func TestCapacityReviewKeys(t *testing.T) {
cover := makePNG(t, 32, 32)
m := ready()
m, _ = step(m, keyDown())
m, _ = step(m, keyDown())
m, _ = step(m, keyEnter())
m, _ = step(m, fileLoadedMsg{origin: stageCover, path: "c.png", data: cover})
if m.stage != stageReview {
t.Fatalf("stage = %v", m.stage)
}
back, _ := step(m, keyEsc())
if back.stage != stageCover {
t.Fatalf("esc from capacity review went to %v", back.stage)
}
fresh, _ := step(m, typeText("n"))
if fresh.stage != stageOperation {
t.Fatalf("n from capacity review went to %v", fresh.stage)
}
}
func TestForwardNonKeyMsgs(t *testing.T) {
for _, s := range []stage{stageCover, stagePayload, stageSecure, stageSave, stagePassphrase, stageResult} {
m := ready()
m.stage = s
m.saving = s == stageResult
m, _ = step(m, nopMsg{})
if got := m.View(); got == "" {
t.Fatalf("empty view after nop msg at stage %v", s)
}
}
}
func TestPassPromptsResetOnCoverLoad(t *testing.T) {
m := ready()
m.op = opReveal
m.stage = stageCover
m.passPrompts = 2
m, _ = step(m, fileLoadedMsg{origin: stageCover, path: "x", data: []byte("not a stego")})
if m.passPrompts != 0 {
t.Fatalf("passPrompts = %d after loading a fresh file, want 0", m.passPrompts)
}
}
func TestHideFitsUsesCompressedSize(t *testing.T) {
cover := makePNG(t, 16, 16)
big := strings.Repeat("A", 200)
m := ready()
m, _ = step(m, keyEnter())
m = pickFormat(t, m, "image")
m, _ = step(m, keyEnter())
m, _ = step(m, fileLoadedMsg{origin: stageCover, path: "c.png", data: cover})
m, _ = step(m, typeText(big))
m, _ = step(m, keyEnter())
m, _ = step(m, keyDown())
m, _ = step(m, keySpace())
if !m.secure.compress {
t.Fatalf("compression not enabled")
}
m, _ = step(m, keyEnter())
m.outPath.SetValue(filepath.Join(t.TempDir(), "o.png"))
m, _ = step(m, keyEnter())
if m.stage != stageReview {
t.Fatalf("stage = %v", m.stage)
}
if len(big)+14 <= m.capValue {
t.Fatalf("precondition broken: raw payload already fits (cap=%d)", m.capValue)
}
if !m.hideFits() {
t.Fatalf("compressible payload should fit: envelope=%d cap=%d", m.envelopeSize, m.capValue)
}
}