Files
hotp_calc/internal/otp/resync_test.go
T

140 lines
4.4 KiB
Go

package otp
import (
"errors"
"testing"
"time"
)
func exampleConfig(t *testing.T) Config {
t.Helper()
s, err := ParseSeed(exampleSeed, FormatAuto)
if err != nil {
t.Fatalf("ParseSeed: %v", err)
}
return Config{Secret: s.Secret, Digits: 6, Alg: SHA1, Step: DefaultStep}
}
func TestFindCounter_RoundTrip(t *testing.T) {
c := exampleConfig(t)
const want = uint64(41)
m, err := FindCounter(c, c.HOTP(want), c.HOTP(want+1), 0, 100000)
if err != nil {
t.Fatalf("FindCounter: %v", err)
}
if len(m) != 1 {
t.Fatalf("найдено %d совпадений, ожидалось 1: %+v", len(m), m)
}
if m[0].Counter != want {
t.Errorf("счётчик = %d, ожидалось %d", m[0].Counter, want)
}
}
func TestFindCounter_RFCVectors(t *testing.T) {
// Пара 287082/359152 из RFC 4226 — это счётчики 1 и 2.
c := Config{Secret: rfc4226Key, Digits: 6, Alg: SHA1, Step: DefaultStep}
m, err := FindCounter(c, "287082", "359152", 0, 1000)
if err != nil {
t.Fatalf("FindCounter: %v", err)
}
if m[0].Counter != 1 {
t.Errorf("счётчик = %d, ожидалось 1", m[0].Counter)
}
}
func TestFindCounter_NotFound(t *testing.T) {
c := exampleConfig(t)
_, err := FindCounter(c, "000000", "000000", 0, 1000)
var nf *ErrNotFound
if !errors.As(err, &nf) {
t.Fatalf("ожидалась ErrNotFound, получено %v", err)
}
}
func TestFindCounter_OutOfRange(t *testing.T) {
c := exampleConfig(t)
// Позиция 500 лежит вне диапазона [0, 100).
if _, err := FindCounter(c, c.HOTP(500), c.HOTP(501), 0, 100); err == nil {
t.Error("ожидалась ошибка: позиция вне диапазона перебора")
}
// С правильным смещением находится.
m, err := FindCounter(c, c.HOTP(500), c.HOTP(501), 450, 100)
if err != nil {
t.Fatalf("FindCounter со смещением: %v", err)
}
if m[0].Counter != 500 {
t.Errorf("счётчик = %d, ожидалось 500", m[0].Counter)
}
}
func TestFindCounter_SpanTooSmall(t *testing.T) {
c := exampleConfig(t)
if _, err := FindCounter(c, "123456", "654321", 0, 1); err == nil {
t.Error("span < 2 должен отвергаться")
}
}
func TestFindCounter_ReversedOrderNotMatched(t *testing.T) {
c := exampleConfig(t)
// Коды, поданные в обратном порядке, не должны находиться.
if _, err := FindCounter(c, c.HOTP(11), c.HOTP(10), 0, 10000); err == nil {
t.Error("перепутанный порядок кодов не должен давать совпадение")
}
}
func TestFindTimeStep_RoundTrip(t *testing.T) {
c := exampleConfig(t)
now := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
// Токен «спешит» на 2 минуты: коды сняты на 4 шага вперёд.
step := c.TimeStep(now) + 4
m, err := FindTimeStep(c, c.HOTP(step), c.HOTP(step+1), now, time.Hour)
if err != nil {
t.Fatalf("FindTimeStep: %v", err)
}
if len(m) != 1 {
t.Fatalf("найдено %d совпадений, ожидалось 1", len(m))
}
if m[0].Counter != step {
t.Errorf("шаг = %d, ожидалось %d", m[0].Counter, step)
}
if want := c.StepStart(step); !m[0].Time.Equal(want) {
t.Errorf("время = %v, ожидалось %v", m[0].Time, want)
}
}
func TestFindTimeStep_Step60(t *testing.T) {
s, _ := ParseSeed(exampleSeed, FormatAuto)
c := Config{Secret: s.Secret, Digits: 6, Alg: SHA1, Step: 60 * time.Second}
now := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
step := c.TimeStep(now) - 10
m, err := FindTimeStep(c, c.HOTP(step), c.HOTP(step+1), now, 24*time.Hour)
if err != nil {
t.Fatalf("FindTimeStep: %v", err)
}
if m[0].Counter != step {
t.Errorf("шаг = %d, ожидалось %d", m[0].Counter, step)
}
}
func TestFindTimeStep_OutsideWindow(t *testing.T) {
c := exampleConfig(t)
now := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
step := c.TimeStep(now) + 10000
if _, err := FindTimeStep(c, c.HOTP(step), c.HOTP(step+1), now, time.Minute); err == nil {
t.Error("ожидалась ошибка: позиция вне окна поиска")
}
}
func BenchmarkFindCounter(b *testing.B) {
s, _ := ParseSeed(exampleSeed, FormatAuto)
c := Config{Secret: s.Secret, Digits: 6, Alg: SHA1, Step: DefaultStep}
o1, o2 := c.HOTP(999998), c.HOTP(999999)
for b.Loop() {
if _, err := FindCounter(c, o1, o2, 0, 1000000); err != nil {
b.Fatal(err)
}
}
}