package cryptext import ( "git.blackforestbytes.com/BlackForestBytes/goext/tst" mathrand "math/rand" "strings" "testing" "unicode" ) func TestPronouncablePasswordLength(t *testing.T) { for _, n := range []int{1, 2, 3, 5, 8, 13, 21, 50, 128} { pw := PronouncablePassword(n) tst.AssertEqual(t, len(pw), n) } } func TestPronouncablePasswordZeroOrNegative(t *testing.T) { tst.AssertEqual(t, PronouncablePassword(0), "") tst.AssertEqual(t, PronouncablePassword(-1), "") tst.AssertEqual(t, PronouncablePassword(-1000), "") } func TestPronouncablePasswordSeededDeterministic(t *testing.T) { pw1 := PronouncablePasswordSeeded(42, 16) pw2 := PronouncablePasswordSeeded(42, 16) tst.AssertEqual(t, pw1, pw2) tst.AssertEqual(t, len(pw1), 16) } func TestPronouncablePasswordSeededDifferentSeeds(t *testing.T) { pw1 := PronouncablePasswordSeeded(1, 16) pw2 := PronouncablePasswordSeeded(2, 16) tst.AssertNotEqual(t, pw1, pw2) } func TestPronouncablePasswordExtEntropy(t *testing.T) { rng := mathrand.New(mathrand.NewSource(1)) pw, entropy := PronouncablePasswordExt(rng, 32) tst.AssertEqual(t, len(pw), 32) if entropy <= 0 { t.Errorf("expected positive entropy, got %f", entropy) } } func TestPronouncablePasswordExtZeroLen(t *testing.T) { rng := mathrand.New(mathrand.NewSource(1)) pw, entropy := PronouncablePasswordExt(rng, 0) tst.AssertEqual(t, pw, "") tst.AssertEqual(t, entropy, float64(0)) } func TestPronouncablePasswordCharacters(t *testing.T) { // Output should be only ASCII letters for i := range 50 { pw := PronouncablePasswordSeeded(int64(i), 32) for _, c := range pw { if !unicode.IsLetter(c) || c > unicode.MaxASCII { t.Errorf("non-letter or non-ASCII rune %q in password %q", c, pw) break } } } } func TestPronouncablePasswordStartsUpper(t *testing.T) { for i := range 50 { pw := PronouncablePasswordSeeded(int64(i), 16) if pw == "" { continue } first := rune(pw[0]) if !unicode.IsUpper(first) { t.Errorf("expected first letter uppercase in %q (seed %d)", pw, i) } if !strings.ContainsRune(ppStartChar, first) { t.Errorf("expected first letter from start-set in %q (seed %d)", pw, i) } } } func TestPpMakeSet(t *testing.T) { set := ppMakeSet("ABC") tst.AssertTrue(t, set['A']) tst.AssertTrue(t, set['B']) tst.AssertTrue(t, set['C']) tst.AssertFalse(t, set['D']) tst.AssertEqual(t, len(set), 3) } func TestPpMakeSetEmpty(t *testing.T) { set := ppMakeSet("") tst.AssertEqual(t, len(set), 0) } func TestPpCharType(t *testing.T) { v, c := ppCharType('A') tst.AssertTrue(t, v) tst.AssertFalse(t, c) v, c = ppCharType('B') tst.AssertFalse(t, v) tst.AssertTrue(t, c) v, c = ppCharType('Y') tst.AssertTrue(t, v) tst.AssertFalse(t, c) v, c = ppCharType('1') tst.AssertFalse(t, v) tst.AssertFalse(t, c) } func TestPpCharsetRemove(t *testing.T) { set := ppMakeSet("AEIOU") out := ppCharsetRemove("ABCDEFG", set, false) tst.AssertEqual(t, out, "BCDFG") } func TestPpCharsetRemoveEmptyDisallowed(t *testing.T) { set := ppMakeSet("AB") out := ppCharsetRemove("AB", set, false) // when result would be empty and allowEmpty=false, it returns the original tst.AssertEqual(t, out, "AB") } func TestPpCharsetRemoveEmptyAllowed(t *testing.T) { set := ppMakeSet("AB") out := ppCharsetRemove("AB", set, true) tst.AssertEqual(t, out, "") } func TestPpCharsetFilter(t *testing.T) { set := ppMakeSet("AEIOU") out := ppCharsetFilter("ABCDEFG", set, false) tst.AssertEqual(t, out, "AE") } func TestPpCharsetFilterEmptyDisallowed(t *testing.T) { set := ppMakeSet("XYZ") out := ppCharsetFilter("ABC", set, false) tst.AssertEqual(t, out, "ABC") // returns original when result empty & not allowed } func TestPpCharsetFilterEmptyAllowed(t *testing.T) { set := ppMakeSet("XYZ") out := ppCharsetFilter("ABC", set, true) tst.AssertEqual(t, out, "") } func TestPronouncablePasswordContinuationFollowsRules(t *testing.T) { // Make sure each continuation pair (lowercased) appears in ppContinuation // Note: when a new segment starts (uppercase letter mid-string), the continuation // check does not apply across the segment boundary. for s := range 30 { seed := int64(s) pw := PronouncablePasswordSeeded(seed, 32) if len(pw) < 2 { continue } runes := []byte(strings.ToUpper(pw)) for i := 1; i < len(runes); i++ { // Detect new segment (original char was uppercase and it's not the first char) origUpper := pw[i] >= 'A' && pw[i] <= 'Z' if origUpper && i > 0 { continue } prev := runes[i-1] cur := runes[i] cont, ok := ppContinuation[prev] if !ok { t.Errorf("no continuation map for %q (pw=%q)", prev, pw) continue } if !strings.ContainsRune(cont, rune(cur)) { t.Errorf("invalid continuation %q -> %q in %q (seed %d)", prev, cur, pw, seed) } } } }