food-market/tests/food-market.UnitTests/PinHasherTests.cs
nns 6b959ac59d
Some checks are pending
Auto-tag / Create date-tag (push) Waiting to run
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
Docker API / Build + push API (push) Waiting to run
Docker API / Deploy API on stage (push) Blocked by required conditions
feat(pos): быстрый вход кассира по PIN (grant_type=pin) + lockout
- Сервер: Employee.PinHash (PBKDF2, миграция Phase36); POST
  /api/organization/employees/{id}/pin — админ задаёт/снимает PIN (4–6 цифр,
  уникален среди активных сотрудников на пересекающихся точках). Кастомный
  OpenIddict-грант grant_type=pin (retail_point_id + pin): находит кассира,
  привязанного к точке, по PIN и выдаёт токены. Брутфорс-защита — in-memory по
  точке (5 неверных → блок 5 мин). Клиент-сидер досоздаёт gt:pin существующим
  клиентам (без пересоздания). Пароль остаётся запасным.
- Касса: PIN-пад на зарегистрированном устройстве (маскированный ввод, 0–9/⌫/
  Войти), переключение на вход по логину; AuthService.PinLoginAsync.
- Тесты: unit PinHasher (9), integration (3): вход по PIN выдаёт рабочий токен;
  5 неверных → блок; дубль PIN на точке → 409. + скриншот PIN-пада в галерее.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 09:38:36 +05:00

41 lines
1.1 KiB
C#

using FluentAssertions;
using foodmarket.Application.Common;
using Xunit;
namespace foodmarket.UnitTests;
public class PinHasherTests
{
[Fact]
public void Hash_then_verify_roundtrips()
{
var hash = PinHasher.Hash("1234");
PinHasher.Verify("1234", hash).Should().BeTrue();
PinHasher.Verify("1235", hash).Should().BeFalse();
}
[Fact]
public void Hash_is_salted_different_each_time()
{
PinHasher.Hash("4321").Should().NotBe(PinHasher.Hash("4321"));
}
[Fact]
public void Verify_handles_null_and_garbage()
{
PinHasher.Verify("1234", null).Should().BeFalse();
PinHasher.Verify("1234", "").Should().BeFalse();
PinHasher.Verify("1234", "not-a-hash").Should().BeFalse();
}
[Theory]
[InlineData("1234", true)]
[InlineData("123456", true)]
[InlineData("123", false)]
[InlineData("1234567", false)]
[InlineData("12a4", false)]
[InlineData(null, false)]
public void IsValidPin_rules(string? pin, bool expected)
=> PinHasher.IsValidPin(pin).Should().Be(expected);
}