Commit aa5bb817 authored by Kevin Lyda's avatar Kevin Lyda
Browse files

Remove dups, add testing

parent e196f8d7
Loading
Loading
Loading
Loading
+65 −0
Original line number Diff line number Diff line
package folders

import (
	"strings"
	"testing"
)

func TestIsAlphaNum(t *testing.T) {
	tests := []struct {
		name string
		in   string
		want bool
	}{
		{"letters only", "hello", true},
		{"digits only", "12345", true},
		{"mixed", "abc123", true},
		{"uppercase", "ABC", true},
		{"empty string", "", true},
		{"with space", "hello world", false},
		{"with underscore", "hello_world", false},
		{"with hyphen", "hello-world", false},
		{"with dot", "file.txt", false},
		{"unicode letters", "héllo", true},
		{"unicode digits", "١٢٣", true},
		{"special chars", "hello!", false},
		{"single letter", "a", true},
		{"single digit", "1", true},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := IsAlphaNum(tt.in)
			if got != tt.want {
				t.Errorf("IsAlphaNum(%q) = %v, want %v", tt.in, got, tt.want)
			}
		})
	}
}

func TestValidateMessageSize(t *testing.T) {
	tests := []struct {
		name    string
		subject string
		message string
		wantErr bool
	}{
		{"both empty", "", "", false},
		{"normal sizes", "Hello", "This is a message.", false},
		{"subject at limit", strings.Repeat("x", MaxSubjectSize), "body", false},
		{"subject over limit", strings.Repeat("x", MaxSubjectSize+1), "body", true},
		{"message at limit", "subj", strings.Repeat("x", MaxMessageSize), false},
		{"message over limit", "subj", strings.Repeat("x", MaxMessageSize+1), true},
		{"both over limit", strings.Repeat("x", MaxSubjectSize+1), strings.Repeat("x", MaxMessageSize+1), true},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := ValidateMessageSize(tt.subject, tt.message)
			if tt.wantErr && err == nil {
				t.Errorf("ValidateMessageSize() expected error, got nil")
			}
			if !tt.wantErr && err != nil {
				t.Errorf("ValidateMessageSize() unexpected error: %v", err)
			}
		})
	}
}

pager/pager_test.go

0 → 100644
+131 −0
Original line number Diff line number Diff line
package pager

import "testing"

func TestSanitizeText(t *testing.T) {
	tests := []struct {
		name string
		in   string
		want string
	}{
		{"plain text", "hello world", "hello world"},
		{"preserves tabs", "hello\tworld", "hello\tworld"},
		{"preserves newlines", "hello\nworld", "hello\nworld"},
		{"preserves carriage return", "hello\rworld", "hello\rworld"},
		{"strips ESC", "hello\x1b[31mworld", "hello[31mworld"},
		{"strips null", "hello\x00world", "helloworld"},
		{"strips bell", "hello\x07world", "helloworld"},
		{"strips DEL", "hello\x7fworld", "helloworld"},
		{"strips C1 controls", "hello\u0080\u009fworld", "helloworld"},
		{"preserves UTF-8", "héllo wörld 日本語", "héllo wörld 日本語"},
		{"empty string", "", ""},
		{"only control chars", "\x01\x02\x03", ""},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := SanitizeText(tt.in)
			if got != tt.want {
				t.Errorf("SanitizeText(%q) = %q, want %q", tt.in, got, tt.want)
			}
		})
	}
}

func TestHighlightLine(t *testing.T) {
	rev := "\033[7m"
	reset := "\033[0m"

	tests := []struct {
		name string
		line string
		term string
		want string
	}{
		{"empty term", "hello world", "", "hello world"},
		{"no match", "hello world", "xyz", "hello world"},
		{"single match", "hello world", "world", "hello " + rev + "world" + reset},
		{"case insensitive", "Hello World", "hello", rev + "Hello" + reset + " World"},
		{"multiple matches", "abcabc", "abc", rev + "abc" + reset + rev + "abc" + reset},
		{"overlapping potential", "aaa", "aa", rev + "aa" + reset + "a"},
		{"match at start", "hello", "hel", rev + "hel" + reset + "lo"},
		{"match at end", "hello", "llo", "he" + rev + "llo" + reset},
		{"entire line", "hello", "hello", rev + "hello" + reset},
		{"empty line", "", "hello", ""},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := highlightLine(tt.line, tt.term)
			if got != tt.want {
				t.Errorf("highlightLine(%q, %q) = %q, want %q", tt.line, tt.term, got, tt.want)
			}
		})
	}
}

func TestSearchForward(t *testing.T) {
	lines := []string{
		"alpha",   // 0
		"bravo",   // 1
		"charlie", // 2
		"delta",   // 3
		"Alpha",   // 4
	}

	tests := []struct {
		name string
		term string
		from int
		want int
	}{
		{"finds first", "alpha", 0, 0},
		{"case insensitive", "BRAVO", 0, 1},
		{"skips earlier lines", "alpha", 1, 4},
		{"from exact match", "charlie", 2, 2},
		{"not found", "zulu", 0, -1},
		{"from past end", "alpha", 10, -1},
		{"negative from clamped", "alpha", -5, 0},
		{"substring match", "rav", 0, 1},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := searchForward(lines, tt.term, tt.from)
			if got != tt.want {
				t.Errorf("searchForward(%q, from=%d) = %d, want %d", tt.term, tt.from, got, tt.want)
			}
		})
	}
}

func TestSearchBackward(t *testing.T) {
	lines := []string{
		"alpha",   // 0
		"bravo",   // 1
		"charlie", // 2
		"delta",   // 3
		"Alpha",   // 4
	}

	tests := []struct {
		name string
		term string
		from int
		want int
	}{
		{"finds last", "alpha", 4, 4},
		{"case insensitive", "ALPHA", 4, 4},
		{"skips later lines", "alpha", 3, 0},
		{"from exact match", "charlie", 2, 2},
		{"not found", "zulu", 4, -1},
		{"from before start", "alpha", -1, -1},
		{"from past end clamped", "alpha", 10, 4},
		{"substring match", "rav", 4, 1},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := searchBackward(lines, tt.term, tt.from)
			if got != tt.want {
				t.Errorf("searchBackward(%q, from=%d) = %d, want %d", tt.term, tt.from, got, tt.want)
			}
		})
	}
}

repl/args_test.go

0 → 100644
+94 −0
Original line number Diff line number Diff line
package repl

import (
	"testing"
	"time"
)

func TestParseNumberList(t *testing.T) {
	tests := []struct {
		name    string
		input   string
		want    []int64
		wantErr bool
	}{
		{"single number", "5", []int64{5}, false},
		{"multiple numbers", "1,2,3", []int64{1, 2, 3}, false},
		{"simple range", "1-5", []int64{1, 2, 3, 4, 5}, false},
		{"mixed", "1,3-5,10", []int64{1, 3, 4, 5, 10}, false},
		{"spaces around commas", " 1 , 2 , 3 ", []int64{1, 2, 3}, false},
		{"spaces around range", " 1 - 3 ", nil, true}, // space before dash makes it not a range parse
		{"single element range", "5-5", []int64{5}, false},
		{"reversed range", "5-1", nil, true},
		{"non-numeric", "abc", nil, true},
		{"range too large", "1-10002", nil, true},
		{"range at limit", "1-10001", []int64(nil), false}, // 10001 elements, end-start=10000
		{"zero", "0", []int64{0}, false},
		{"large number", "999999", []int64{999999}, false},
		{"invalid in list", "1,abc,3", nil, true},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := ParseNumberList(tt.input)
			if tt.wantErr {
				if err == nil {
					t.Errorf("ParseNumberList(%q) expected error, got %v", tt.input, got)
				}
				return
			}
			if err != nil {
				t.Errorf("ParseNumberList(%q) unexpected error: %v", tt.input, err)
				return
			}
			if tt.want != nil {
				if len(got) != len(tt.want) {
					t.Errorf("ParseNumberList(%q) got %d elements, want %d", tt.input, len(got), len(tt.want))
					return
				}
				for i := range got {
					if got[i] != tt.want[i] {
						t.Errorf("ParseNumberList(%q)[%d] = %d, want %d", tt.input, i, got[i], tt.want[i])
					}
				}
			}
		})
	}
}

func TestParseDate(t *testing.T) {
	tests := []struct {
		name    string
		input   string
		wantY   int
		wantM   time.Month
		wantD   int
		wantErr bool
	}{
		{"valid date", "2025-03-15", 2025, time.March, 15, false},
		{"leap day", "2024-02-29", 2024, time.February, 29, false},
		{"epoch", "1970-01-01", 1970, time.January, 1, false},
		{"wrong format slash", "2025/03/15", 0, 0, 0, true},
		{"wrong format DMY", "15-03-2025", 0, 0, 0, true},
		{"incomplete", "2025-03", 0, 0, 0, true},
		{"empty", "", 0, 0, 0, true},
		{"garbage", "not-a-date", 0, 0, 0, true},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := ParseDate(tt.input)
			if tt.wantErr {
				if err == nil {
					t.Errorf("ParseDate(%q) expected error, got %v", tt.input, got)
				}
				return
			}
			if err != nil {
				t.Errorf("ParseDate(%q) unexpected error: %v", tt.input, err)
				return
			}
			if got.Year() != tt.wantY || got.Month() != tt.wantM || got.Day() != tt.wantD {
				t.Errorf("ParseDate(%q) = %v, want %d-%02d-%02d", tt.input, got, tt.wantY, tt.wantM, tt.wantD)
			}
		})
	}
}
+12 −44
Original line number Diff line number Diff line
@@ -876,10 +876,7 @@ func ActionReply(cmd *dclish.Command) error {
	return nil
}

// ActionSeen handles the `SEEN` command.  Marks messages as seen.
//
// This originally existed as... not sure.
func ActionSeen(cmd *dclish.Command) error {
func modifyMsgAttributes(cmd *dclish.Command, modify func([]int64) error) error {
	var err error
	msgids := []int64{this.MsgID}
	if len(cmd.Args) == 1 {
@@ -888,30 +885,25 @@ func ActionSeen(cmd *dclish.Command) error {
			return err
		}
	}
	err = folders.MarkSeen(msgids)
	err = modify(msgids)
	if err != nil {
		fmt.Printf("ERROR: %s.\n", err)
	}
	return nil
}

// ActionSeen handles the `SEEN` command.  Marks messages as seen.
//
// This originally existed as... not sure.
func ActionSeen(cmd *dclish.Command) error {
	return modifyMsgAttributes(cmd, folders.MarkSeen)
}

// ActionUnseen handles the `UNSEEN` command. Marks messages as unseen.
//
// This originally existed as... not sure.
func ActionUnseen(cmd *dclish.Command) error {
	var err error
	msgids := []int64{this.MsgID}
	if len(cmd.Args) == 1 {
		msgids, err = ParseNumberList(cmd.Args[0])
		if err != nil {
			return err
		}
	}
	err = folders.MarkUnseen(msgids)
	if err != nil {
		return err
	}
	return nil
	return modifyMsgAttributes(cmd, folders.MarkUnseen)
}

// ActionDelete handles the `DELETE` command.  This deletes a message.
@@ -966,19 +958,7 @@ func ActionDelete(cmd *dclish.Command) error {
//
// This originally existed as... not sure.
func ActionMark(cmd *dclish.Command) error {
	var err error
	msgids := []int64{this.MsgID}
	if len(cmd.Args) == 1 {
		msgids, err = ParseNumberList(cmd.Args[0])
		if err != nil {
			return err
		}
	}
	err = folders.SetMark(msgids)
	if err != nil {
		fmt.Printf("ERROR: %s.\n", err)
	}
	return nil
	return modifyMsgAttributes(cmd, folders.SetMark)
}

// ActionUnmark handles the `UNMARK` command.  This removes a MARK on
@@ -986,19 +966,7 @@ func ActionMark(cmd *dclish.Command) error {
//
// This originally existed as... not sure.
func ActionUnmark(cmd *dclish.Command) error {
	var err error
	msgids := []int64{this.MsgID}
	if len(cmd.Args) == 1 {
		msgids, err = ParseNumberList(cmd.Args[0])
		if err != nil {
			return err
		}
	}
	err = folders.UnsetMark(msgids)
	if err != nil {
		fmt.Printf("ERROR: %s.\n", err)
	}
	return nil
	return modifyMsgAttributes(cmd, folders.UnsetMark)
}

// ActionSearch handles the `SEARCH` command.  This will show all messages

users/users_test.go

0 → 100644
+39 −0
Original line number Diff line number Diff line
package users

import "testing"

func TestValidLogin(t *testing.T) {
	tests := []struct {
		name    string
		login   string
		wantErr bool
	}{
		{"valid uppercase", "ADMIN", false},
		{"valid with digits", "USER123", false},
		{"valid with underscore", "USER_NAME", false},
		{"valid with hyphen", "USER-NAME", false},
		{"valid max length", "ABCDEFGHIJKL", false},
		{"too long", "ABCDEFGHIJKLM", true},
		{"empty", "", true},
		{"lowercase", "admin", true},
		{"mixed case", "Admin", true},
		{"contains space", "USER NAME", true},
		{"contains dot", "USER.NAME", true},
		{"contains slash", "USER/NAME", true},
		{"single char", "A", false},
		{"all digits", "123456", false},
		{"all underscores", "____", false},
		{"special chars", "USER@NAME", true},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := ValidLogin(tt.login)
			if tt.wantErr && err == nil {
				t.Errorf("ValidLogin(%q) expected error, got nil", tt.login)
			}
			if !tt.wantErr && err != nil {
				t.Errorf("ValidLogin(%q) unexpected error: %v", tt.login, err)
			}
		})
	}
}