using FluentAssertions;
using foodmarket.Api.Infrastructure.Invoices;
using Xunit;
namespace foodmarket.UnitTests;
/// Проверка парсинга ответа Claude CLI. CLI отдаёт конверт
/// { result: "…json…", usage: {...} }; result иногда обёрнут в
/// ```json. Нужно вытащить payload и привести к ParsedInvoice.
public class ClaudeCliInvoiceExtractorParseTests
{
[Fact]
public void Parse_plain_json_payload()
{
var json = """{"supplier":"ТОО Фуд","number":"Н-1","date":"2026-10-04","total":1500.00,"lines":[{"barcode":"4870123456789","supplier_code":"ABC","name":"Молоко 3.2%","qty":10,"unit":"шт","price":150.00,"sum":1500.00,"vat":12}]}""";
var inv = ClaudeCliInvoiceExtractor.Parse(json);
inv.SupplierName.Should().Be("ТОО Фуд");
inv.InvoiceNumber.Should().Be("Н-1");
inv.TotalSum.Should().Be(1500m);
inv.Lines.Should().HaveCount(1);
var l = inv.Lines[0];
l.Barcode.Should().Be("4870123456789");
l.SupplierCode.Should().Be("ABC");
l.Quantity.Should().Be(10m);
l.Price.Should().Be(150m);
}
[Fact]
public void Parse_envelope_with_result_string_and_markdown_fence()
{
// Payload wrapped в ```json ``` — Claude иногда так возвращает.
var fence = "`" + "`" + "`";
var payloadInside = fence + "json\n{\"supplier\":\"X\",\"lines\":[{\"name\":\"А\",\"qty\":1,\"price\":10}]}\n" + fence;
var envelope = System.Text.Json.JsonSerializer.Serialize(new
{
result = payloadInside,
usage = new { input_tokens = 123, output_tokens = 45 }
});
var inv = ClaudeCliInvoiceExtractor.Parse(envelope);
inv.SupplierName.Should().Be("X");
inv.Lines.Should().ContainSingle();
inv.InputTokens.Should().Be(123);
inv.OutputTokens.Should().Be(45);
}
[Fact]
public void Parse_barcode_with_spaces_is_stripped()
{
var json = """{"lines":[{"name":"X","qty":1,"price":1,"barcode":"4870 123 456 789"}]}""";
var inv = ClaudeCliInvoiceExtractor.Parse(json);
inv.Lines[0].Barcode.Should().Be("4870123456789");
}
[Fact]
public void Parse_null_fields_tolerated()
{
var json = """{"supplier":null,"number":null,"date":null,"total":null,"lines":[{"name":"X","qty":"2,5","price":"100.5"}]}""";
var inv = ClaudeCliInvoiceExtractor.Parse(json);
inv.SupplierName.Should().BeNull();
inv.TotalSum.Should().BeNull();
inv.Lines[0].Quantity.Should().Be(2.5m); // «2,5» → 2.5
inv.Lines[0].Price.Should().Be(100.5m);
}
[Fact]
public void Parse_empty_raises()
{
var act = () => ClaudeCliInvoiceExtractor.Parse("");
act.Should().Throw();
}
[Fact]
public void Parse_lines_without_name_are_skipped()
{
var json = """{"lines":[{"name":"A","qty":1,"price":1},{"name":"","qty":2,"price":2},{"name":null,"qty":3,"price":3}]}""";
var inv = ClaudeCliInvoiceExtractor.Parse(json);
inv.Lines.Should().ContainSingle(l => l.Name == "A");
}
}