Cybersecurity-Projects/PROJECTS/beginner/steganography-multi-tool/internal/cli/interactive_test.go

199 lines
5.8 KiB
Go

/*
©AngelaMos | 2026
interactive_test.go
Tests for passphrase prompting, reprompt loops, and output branches
*/
package cli
import (
"errors"
"io"
"os"
"path/filepath"
"testing"
"github.com/CarterPerez-dev/crypha/internal/payload"
)
func stubTerminal(t *testing.T, inter bool, secrets ...string) {
t.Helper()
origInter, origRead := interactive, readSecret
t.Cleanup(func() { interactive, readSecret = origInter, origRead })
interactive = func() bool { return inter }
idx := 0
readSecret = func() ([]byte, error) {
if idx >= len(secrets) {
return nil, io.EOF
}
s := secrets[idx]
idx++
return []byte(s), nil
}
}
func TestPromptForError(t *testing.T) {
if got := promptForError(payload.ErrPassphraseRequired); got != promptEnterPassphrase {
t.Errorf("passphrase-required prompt = %q", got)
}
if got := promptForError(payload.ErrDecrypt); got != promptRetryPassphrase {
t.Errorf("decrypt prompt = %q", got)
}
}
func TestInteractiveHidePromptRoundTrip(t *testing.T) {
t.Setenv(envPassphrase, "")
stubTerminal(t, true, "prompted-secret", "prompted-secret")
dir := t.TempDir()
cover := makePNGCover(t, dir)
stego := filepath.Join(dir, "prompted.png")
if _, _, err := run(t, "", "hide", "--format", "image", "-i", cover, "-o", stego, "-m", secret, "--encrypt"); err != nil {
t.Fatalf("hide with prompt: %v", err)
}
out, _, err := run(t, "", "reveal", "-i", stego, "-k", "prompted-secret")
if err != nil || out != secret {
t.Fatalf("reveal = %q err %v", out, err)
}
}
func TestInteractiveHidePromptMismatch(t *testing.T) {
t.Setenv(envPassphrase, "")
stubTerminal(t, true, "one", "two")
dir := t.TempDir()
cover := makePNGCover(t, dir)
stego := filepath.Join(dir, "mismatch.png")
_, _, err := run(t, "", "hide", "--format", "image", "-i", cover, "-o", stego, "-m", secret, "--encrypt")
if !errors.Is(err, errPassphraseMismatch) {
t.Fatalf("err = %v, want errPassphraseMismatch", err)
}
}
func TestHideEncryptNonInteractiveNoKey(t *testing.T) {
t.Setenv(envPassphrase, "")
stubTerminal(t, false)
dir := t.TempDir()
cover := makePNGCover(t, dir)
stego := filepath.Join(dir, "nokey.png")
_, _, err := run(t, "", "hide", "--format", "image", "-i", cover, "-o", stego, "-m", secret, "--encrypt")
if !errors.Is(err, errPassphraseNonInteractive) {
t.Fatalf("err = %v, want errPassphraseNonInteractive", err)
}
}
func TestRevealReprompt(t *testing.T) {
t.Setenv(envPassphrase, "")
dir := t.TempDir()
cover := makePNGCover(t, dir)
stego := filepath.Join(dir, "reprompt.png")
if _, _, err := run(t, "", "hide", "--format", "image", "-i", cover, "-o", stego, "-m", secret, "-k", "rightpass"); err != nil {
t.Fatalf("hide: %v", err)
}
stubTerminal(t, true, "wrongpass", "rightpass")
out, _, err := run(t, "", "reveal", "-i", stego)
if err != nil || out != secret {
t.Fatalf("reveal after reprompt = %q err %v", out, err)
}
}
func TestRevealRepromptExhausted(t *testing.T) {
t.Setenv(envPassphrase, "")
dir := t.TempDir()
cover := makePNGCover(t, dir)
stego := filepath.Join(dir, "exhausted.png")
if _, _, err := run(t, "", "hide", "--format", "image", "-i", cover, "-o", stego, "-m", secret, "-k", "rightpass"); err != nil {
t.Fatalf("hide: %v", err)
}
stubTerminal(t, true, "no", "no", "no")
_, _, err := run(t, "", "reveal", "-i", stego)
if !errors.Is(err, payload.ErrDecrypt) {
t.Fatalf("err = %v, want ErrDecrypt after exhausting attempts", err)
}
}
func TestRevealToFile(t *testing.T) {
dir := t.TempDir()
cover := makeTextCover(t, dir)
stego := filepath.Join(dir, "tofile.txt")
if _, _, err := run(t, "", "hide", "--format", "text", "-i", cover, "-o", stego, "-m", secret); err != nil {
t.Fatalf("hide: %v", err)
}
out := filepath.Join(dir, "revealed.txt")
stdout, _, err := run(t, "", "reveal", "-i", stego, "-o", out)
if err != nil {
t.Fatalf("reveal -o: %v", err)
}
if stdout != "" {
t.Errorf("stdout should be empty when writing to a file, got %q", stdout)
}
got, err := os.ReadFile(out)
if err != nil {
t.Fatalf("read revealed file: %v", err)
}
if string(got) != secret {
t.Errorf("revealed file = %q, want %q", got, secret)
}
}
func TestBuildOptionsUnknownStrength(t *testing.T) {
dir := t.TempDir()
cover := makePNGCover(t, dir)
out := filepath.Join(dir, "out.png")
_, _, err := run(t, "", "hide", "--format", "image", "-i", cover, "-o", out, "-m", "x", "-k", "p", "--strength", "ultra")
if err == nil || !errors.Is(err, errUnknownStrength) {
t.Fatalf("err = %v, want errUnknownStrength", err)
}
}
func TestCapacityUnknownFormat(t *testing.T) {
dir := t.TempDir()
cover := makePNGCover(t, dir)
_, _, err := run(t, "", "capacity", "-i", cover, "--format", "bogus")
if err == nil {
t.Fatal("expected error for unknown format")
}
}
func TestMissingFileErrors(t *testing.T) {
dir := t.TempDir()
missing := filepath.Join(dir, "does-not-exist")
out := filepath.Join(dir, "out.png")
cases := [][]string{
{"hide", "--format", "image", "-i", missing, "-o", out, "-m", "x"},
{"reveal", "-i", missing},
{"capacity", "-i", missing},
}
for _, args := range cases {
if _, _, err := run(t, "", args...); err == nil {
t.Errorf("%v: expected error for missing file", args)
}
}
}
func TestRevealPromptReadError(t *testing.T) {
t.Setenv(envPassphrase, "")
dir := t.TempDir()
cover := makePNGCover(t, dir)
stego := filepath.Join(dir, "readerr.png")
if _, _, err := run(t, "", "hide", "--format", "image", "-i", cover, "-o", stego, "-m", secret, "-k", "pw"); err != nil {
t.Fatalf("hide: %v", err)
}
origInter, origRead := interactive, readSecret
t.Cleanup(func() { interactive, readSecret = origInter, origRead })
interactive = func() bool { return true }
readSecret = func() ([]byte, error) { return nil, io.ErrUnexpectedEOF }
if _, _, err := run(t, "", "reveal", "-i", stego); !errors.Is(err, io.ErrUnexpectedEOF) {
t.Fatalf("err = %v, want a terminal read error", err)
}
}