Files
go-caatsm/internal/adapter/parser/aviation/tokenizer_test.go
T
windyboyandClaude Sonnet 4.5 c0a66cf845 Enhance aviation parser with security fixes and comprehensive refactoring
This commit implements a complete refactoring of the ICAO aviation parser,
addressing 15 identified issues across security, performance, code quality,
and documentation.

Security Enhancements (P0 - Critical):
- Add input size validation (max 1800 chars per AFTN standard)
- Implement ReDoS protection with 100ms regex timeout mechanism
- Add field validation to prevent nil pointer dereferences
- Document intentional error handling pattern for audit compliance

Performance & Design Improvements (P1 - Important):
- Remove unnecessary mutex from BodyParser (eliminates serialization)
- Fix tokenizer slash handling logic
- Remove global logger dependencies (zap.S() calls)

Code Quality Improvements (P2):
- Refactor parseRemainingLines with clear helper functions
- Document all regex patterns with ICAO format specifications
- Replace magic numbers with named constants (5 new constants)
- Add error message sanitization to prevent data leakage

Documentation & Polish (P3):
- Create comprehensive package documentation (doc.go)
- Verify naming consistency across all functions
- Add 54 comprehensive tests (all passing)
- Verify performance with benchmarks (~10µs for simple messages)

New Files:
- validation.go: Input validation utilities with AFTN limits
- validation_test.go: Comprehensive validation tests
- regex_timeout.go: ReDoS protection mechanism
- regex_timeout_test.go: Timeout protection tests
- suite_test.go: Ginkgo test suite registration
- doc.go: Package-level documentation

All changes maintain backward compatibility and existing architecture
while significantly enhancing security, maintainability, and code quality.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-26 17:55:25 +08:00

129 lines
3.0 KiB
Go

package aviation
import (
"caatsm/internal/domain"
"testing"
)
func TestTokenizerDefaultWhitespace(t *testing.T) {
t.Parallel()
input := "A B\nC\tD\rE"
tokens := Tokenizer{}.Tokenize(input)
expected := []Token{
{Text: "A", Start: 0, End: 1},
{Text: "B", Start: 2, End: 3},
{Text: "C", Start: 4, End: 5},
{Text: "D", Start: 6, End: 7},
{Text: "E", Start: 8, End: 9},
}
if len(tokens) != len(expected) {
t.Fatalf("expected %d tokens, got %d", len(expected), len(tokens))
}
for i, token := range tokens {
if token != expected[i] {
t.Fatalf("token %d mismatch: got %#v, expected %#v", i, token, expected[i])
}
}
}
func TestTokenizerSlashWhitespace(t *testing.T) {
t.Parallel()
// When slash is in whitespace, it splits tokens but is not emitted
input := "A/B C"
tokens := Tokenizer{Whitespace: " \n\t\r/"}.Tokenize(input)
expected := []Token{
{Text: "A", Start: 0, End: 1},
{Text: "B", Start: 2, End: 3},
{Text: "C", Start: 4, End: 5},
}
if len(tokens) != len(expected) {
t.Fatalf("expected %d tokens, got %d", len(expected), len(tokens))
}
for i, token := range tokens {
if token != expected[i] {
t.Fatalf("token %d mismatch: got %#v, expected %#v", i, token, expected[i])
}
}
}
func TestBuildBodyPatternsIncludesCategories(t *testing.T) {
t.Parallel()
patterns := buildBodyPatterns()
for _, category := range []string{
CategoryArrival,
CategoryDeparture,
CategoryCancellation,
CategoryDelay,
CategoryFlightPlan,
} {
config, ok := patterns[category]
if !ok {
t.Fatalf("expected category %s in body patterns", category)
}
if len(config.Patterns) == 0 || config.Patterns[0].Expression == nil {
t.Fatalf("expected pattern expression for category %s", category)
}
}
}
func TestParseCategoryInvalid(t *testing.T) {
t.Parallel()
_, err := parseCategory("XYZ", ParseContext{}, map[string]string{})
if err == nil {
t.Fatal("expected error for invalid category")
}
}
func TestParseCategoryFPL(t *testing.T) {
t.Parallel()
body := `(FPL-CCA1532-IS
-A332/H
-SDE3FGHIJ4J5M1RWY/LB101
-ZSSS2035
-K0859S1040 PIAKS G330 PIMOL A539 BTO W82 DOGAR
-ZBAA0153 ZBYN
-PBN/A1B2B3B4B5D1L1 NAV/ABAS REG/B6513 EET/ZBPE0112 SEL/KMAL PER/C RIF/FRT N640 ZBYN RMK/TCAS EQUIPPED)`
data := extract(body, FplPatternExpression)
if data == nil {
t.Fatal("expected FPL pattern to match")
}
parsed, err := parseCategory(CategoryFlightPlan, ParseContext{
Body: body,
Tokens: Tokenizer{}.Tokenize(body),
}, data)
if err != nil {
t.Fatalf("unexpected parse error: %v", err)
}
fpl, ok := parsed.(*domain.FPL)
if !ok {
t.Fatalf("expected *domain.FPL, got %T", parsed)
}
if fpl.FlightNumber != "CCA1532" {
t.Fatalf("expected flight number CCA1532, got %s", fpl.FlightNumber)
}
if fpl.PBN != "A1B2B3B4B5D1L1" {
t.Fatalf("expected PBN A1B2B3B4B5D1L1, got %s", fpl.PBN)
}
if fpl.RerouteInformation != "FRT N640 ZBYN" {
t.Fatalf("expected reroute information, got %s", fpl.RerouteInformation)
}
if fpl.Remarks != "TCAS EQUIPPED" {
t.Fatalf("expected remarks TCAS EQUIPPED, got %s", fpl.Remarks)
}
}