feat(m2m): доменная модель сообщений + парсер windows-1251 + round-trip тесты
- internal/m2m/types.go: enum'ы и simple-типы из XSD НРД (M2MSchemas_260408) - internal/m2m/validators.go: pattern-валидаторы ReferenceID/ISIN/INN/UUID/SecurityCode/IdentityDocSerial/AccountID + перечисления - internal/m2m/messages.go: структуры 6 типов сообщений M2M, choice-типы через указатели, IsM2M=true автоматически в MarshalXML - internal/nsdxml/datetime.go: тип NSDDateTime (формат "YYYY-MM-DDThh:mm:ss(МСК+N)") - internal/nsdxml/codec.go: Marshal/Unmarshal XML в windows-1251 (собственный кодек CP1251, без внешних зависимостей) - internal/m2m/messages_test.go: round-trip тесты на 6 примерах + 2 эталонах из DOC/ Покрытие: m2m 73.9%, nsdxml 92.5%. make ci зелёный. Отклонение от спеки: вместо golang.org/x/text/encoding/charmap собственная таблица CP1251 на ~60 строк, потому что прокси zetit блокирует proxy.golang.org, goproxy.cn и redirect-хосты Go-модулей. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
package nsdxml_test
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/nsdxml"
|
||||
)
|
||||
|
||||
func TestNSDDateTimeParse(t *testing.T) {
|
||||
cases := []struct {
|
||||
in string
|
||||
expectOffset int
|
||||
expectSpecified bool
|
||||
}{
|
||||
{"2026-03-02T14:30:45(МСК)", 0, false},
|
||||
{"2026-03-02T14:30:45(МСК+2)", 2, true},
|
||||
{"2026-03-02T14:30:45(МСК-1)", -1, true},
|
||||
{"2026-03-02T14:30:45(МСК+12)", 12, true},
|
||||
}
|
||||
for _, c := range cases {
|
||||
c := c
|
||||
t.Run(c.in, func(t *testing.T) {
|
||||
var d nsdxml.NSDDateTime
|
||||
if err := d.UnmarshalText([]byte(c.in)); err != nil {
|
||||
t.Fatalf("UnmarshalText: %v", err)
|
||||
}
|
||||
if d.OffsetSpecified != c.expectSpecified {
|
||||
t.Errorf("OffsetSpecified = %v, ожидалось %v", d.OffsetSpecified, c.expectSpecified)
|
||||
}
|
||||
if d.OffsetHours != c.expectOffset {
|
||||
t.Errorf("OffsetHours = %d, ожидалось %d", d.OffsetHours, c.expectOffset)
|
||||
}
|
||||
out := d.String()
|
||||
if out != c.in {
|
||||
t.Errorf("String() = %q, ожидалось %q", out, c.in)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNSDDateTimeNow(t *testing.T) {
|
||||
d := nsdxml.Now()
|
||||
if d.OffsetSpecified {
|
||||
t.Errorf("Now() не должен задавать смещение")
|
||||
}
|
||||
if zone, _ := d.Time.Zone(); zone != "МСК" {
|
||||
t.Errorf("Now() location зона = %q, ожидалось \"МСК\"", zone)
|
||||
}
|
||||
if time.Since(d.Time) > time.Minute {
|
||||
t.Errorf("Now() вернул время, далёкое от текущего")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNSDDateTimeXMLRoundTrip(t *testing.T) {
|
||||
type wrap struct {
|
||||
XMLName xml.Name `xml:"wrap"`
|
||||
TS nsdxml.NSDDateTime `xml:"ts"`
|
||||
}
|
||||
src := `<wrap><ts>2026-03-02T14:30:45(МСК+2)</ts></wrap>`
|
||||
var w wrap
|
||||
if err := xml.Unmarshal([]byte(src), &w); err != nil {
|
||||
t.Fatalf("Unmarshal: %v", err)
|
||||
}
|
||||
if w.TS.OffsetHours != 2 {
|
||||
t.Errorf("OffsetHours = %d, ожидалось 2", w.TS.OffsetHours)
|
||||
}
|
||||
b, err := xml.Marshal(w)
|
||||
if err != nil {
|
||||
t.Fatalf("Marshal: %v", err)
|
||||
}
|
||||
if !strings.Contains(string(b), "2026-03-02T14:30:45(МСК+2)") {
|
||||
t.Errorf("Marshal не сохранил формат: %s", b)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNSDDateTimeParseErrors(t *testing.T) {
|
||||
bad := []string{
|
||||
"",
|
||||
"2026-13-02T14:30:45(МСК)",
|
||||
"2026-03-02 14:30:45(МСК)",
|
||||
"2026-03-02T14:30:45",
|
||||
"2026-03-02T14:30:45(MSK)",
|
||||
"2026-03-02T14:30:45(МСК+200)",
|
||||
}
|
||||
for _, s := range bad {
|
||||
s := s
|
||||
t.Run(s, func(t *testing.T) {
|
||||
var d nsdxml.NSDDateTime
|
||||
if err := d.UnmarshalText([]byte(s)); err == nil {
|
||||
t.Errorf("ожидалась ошибка для %q", s)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user