186 lines
5.9 KiB
Go
186 lines
5.9 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"flag"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func testFlagSet() *flag.FlagSet {
|
|
fs := flag.NewFlagSet("t", flag.ContinueOnError)
|
|
fs.Int("n", 10, "")
|
|
fs.String("seed", "", "")
|
|
fs.Bool("json", false, "")
|
|
return fs
|
|
}
|
|
|
|
func TestPermute(t *testing.T) {
|
|
cases := []struct {
|
|
in []string
|
|
want []string
|
|
}{
|
|
{[]string{"SEED", "111111", "222222", "-n", "4"},
|
|
[]string{"-n", "4", "SEED", "111111", "222222"}},
|
|
{[]string{"-n", "4", "SEED"}, []string{"-n", "4", "SEED"}},
|
|
{[]string{"SEED", "-json", "111111"}, []string{"-json", "SEED", "111111"}},
|
|
{[]string{"SEED", "-n=4"}, []string{"-n=4", "SEED"}},
|
|
{[]string{"--", "-n", "4"}, []string{"-n", "4"}},
|
|
{[]string{}, []string{}},
|
|
}
|
|
for _, tc := range cases {
|
|
got := permute(testFlagSet(), tc.in)
|
|
if strings.Join(got, " ") != strings.Join(tc.want, " ") {
|
|
t.Errorf("permute(%v) = %v, ожидалось %v", tc.in, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAssignPositional(t *testing.T) {
|
|
// Все три значения позиционно.
|
|
var o options
|
|
if err := assignPositional(&o, []string{"SEED", "111111", "222222"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if o.seed != "SEED" || o.otp1 != "111111" || o.otp2 != "222222" {
|
|
t.Errorf("получено %+v", o)
|
|
}
|
|
|
|
// seed задан флагом — позиционные аргументы должны стать кодами.
|
|
o = options{seed: "SEED"}
|
|
if err := assignPositional(&o, []string{"111111", "222222"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if o.otp1 != "111111" || o.otp2 != "222222" {
|
|
t.Errorf("seed из флага: получено otp1=%q otp2=%q", o.otp1, o.otp2)
|
|
}
|
|
|
|
// Коды заданы флагами — позиционный аргумент должен стать seed.
|
|
o = options{otp1: "111111", otp2: "222222"}
|
|
if err := assignPositional(&o, []string{"SEED"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if o.seed != "SEED" {
|
|
t.Errorf("seed = %q", o.seed)
|
|
}
|
|
|
|
// Больше аргументов, чем свободных полей.
|
|
o = options{}
|
|
if err := assignPositional(&o, []string{"SEED", "1", "2", "3"}); err == nil {
|
|
t.Error("лишний аргумент должен отвергаться")
|
|
}
|
|
o = options{seed: "SEED", otp1: "1", otp2: "2"}
|
|
if err := assignPositional(&o, []string{"X"}); err == nil {
|
|
t.Error("лишний аргумент при всех заданных флагах должен отвергаться")
|
|
}
|
|
}
|
|
|
|
func TestParseWindow(t *testing.T) {
|
|
cases := map[string]time.Duration{
|
|
"90d": 90 * 24 * time.Hour,
|
|
"2w": 14 * 24 * time.Hour,
|
|
"12h": 12 * time.Hour,
|
|
"90m": 90 * time.Minute,
|
|
"1h30m": 90 * time.Minute,
|
|
}
|
|
for in, want := range cases {
|
|
got, err := parseWindow(in)
|
|
if err != nil || got != want {
|
|
t.Errorf("parseWindow(%q) = %v, %v; ожидалось %v", in, got, err, want)
|
|
}
|
|
}
|
|
for _, in := range []string{"", "abc", "5x", "d"} {
|
|
if _, err := parseWindow(in); err == nil {
|
|
t.Errorf("parseWindow(%q) должен возвращать ошибку", in)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestValidateCode(t *testing.T) {
|
|
for _, ok := range []string{"755224", "00000000"} {
|
|
if err := validateCode(ok, "код"); err != nil {
|
|
t.Errorf("validateCode(%q): %v", ok, err)
|
|
}
|
|
}
|
|
for _, bad := range []string{"", "12345", "12345678901", "12a456", "755 224"} {
|
|
if err := validateCode(bad, "код"); err == nil {
|
|
t.Errorf("validateCode(%q) должен возвращать ошибку", bad)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseMode(t *testing.T) {
|
|
if v, err := parseMode("TOTP"); err != nil || !v {
|
|
t.Errorf("parseMode(TOTP) = %v, %v", v, err)
|
|
}
|
|
if v, err := parseMode(""); err != nil || v {
|
|
t.Errorf("parseMode(\"\") = %v, %v", v, err)
|
|
}
|
|
if _, err := parseMode("ocra"); err == nil {
|
|
t.Error("неизвестный режим должен отвергаться")
|
|
}
|
|
}
|
|
|
|
func TestPlural(t *testing.T) {
|
|
cases := map[int64]string{1: "шаг", 2: "шага", 5: "шагов", 11: "шагов",
|
|
13: "шагов", 21: "шаг", 22: "шага", 25: "шагов", 101: "шаг", 112: "шагов"}
|
|
for n, want := range cases {
|
|
if got := plural(n, "шаг", "шага", "шагов"); got != want {
|
|
t.Errorf("plural(%d) = %q, ожидалось %q", n, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPrintText_HOTP(t *testing.T) {
|
|
r := result{
|
|
Mode: "hotp", Algorithm: "SHA1", Digits: 6, SeedBytes: 20,
|
|
SeedShown: "765D7268…382D", SeedForm: "hex",
|
|
OTP1: "941595", OTP2: "057938",
|
|
Positions: []position{{
|
|
Counter: 41,
|
|
Codes: []entry{{Index: 1, Counter: 43, Code: "937300"}},
|
|
}},
|
|
}
|
|
var buf bytes.Buffer
|
|
printText(&buf, r, false)
|
|
out := buf.String()
|
|
for _, want := range []string{"HOTP, SHA1, 6 разрядов", "счётчик 41", "счётчик 42", "937300", "Счётчик"} {
|
|
if !strings.Contains(out, want) {
|
|
t.Errorf("вывод не содержит %q:\n%s", want, out)
|
|
}
|
|
}
|
|
if strings.Contains(out, "Внимание") {
|
|
t.Errorf("для 20-байтного seed предупреждения быть не должно:\n%s", out)
|
|
}
|
|
}
|
|
|
|
func TestPrintText_TOTPAndWarnings(t *testing.T) {
|
|
from := time.Date(2026, 1, 1, 0, 3, 0, 0, time.UTC)
|
|
r := result{
|
|
Mode: "totp", Algorithm: "SHA1", Digits: 6, StepSeconds: 30, SeedBytes: 10,
|
|
SeedShown: "3132…3930", SeedForm: "base32",
|
|
OTP1: "328000", OTP2: "787414", Ambiguous: true,
|
|
Positions: []position{{
|
|
Counter: 58907524, DriftSteps: 5, DriftSeconds: 150,
|
|
Codes: []entry{{Index: 1, Counter: 58907526, Code: "434361",
|
|
ValidFrom: from, ValidTo: from.Add(30 * time.Second), Current: true}},
|
|
}},
|
|
}
|
|
var buf bytes.Buffer
|
|
printText(&buf, r, true)
|
|
out := buf.String()
|
|
for _, want := range []string{
|
|
"TOTP, SHA1, 6 разрядов, шаг 30 с",
|
|
"шаг 58907524",
|
|
"опережает эту машину примерно на 150 с (5 шагов)",
|
|
"действует сейчас",
|
|
"ожидается 20 байт",
|
|
"несколько позиций",
|
|
} {
|
|
if !strings.Contains(out, want) {
|
|
t.Errorf("вывод не содержит %q:\n%s", want, out)
|
|
}
|
|
}
|
|
}
|