181 lines
4.7 KiB
Go
181 lines
4.7 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|
|
}
|