|
| 1 | +package cli |
| 2 | + |
| 3 | +import ( |
| 4 | + "strings" |
| 5 | + "testing" |
| 6 | +) |
| 7 | + |
| 8 | +func TestSplitProjectionPathWithArrayIndices(t *testing.T) { |
| 9 | + got := splitProjectionPath("results.environments[0].dimensions[1].metrics") |
| 10 | + want := []string{"results", "environments", "0", "dimensions", "1", "metrics"} |
| 11 | + if len(got) != len(want) { |
| 12 | + t.Fatalf("unexpected segment count: got=%d want=%d (%v)", len(got), len(want), got) |
| 13 | + } |
| 14 | + for i := range want { |
| 15 | + if got[i] != want[i] { |
| 16 | + t.Fatalf("segment %d mismatch: got=%q want=%q", i, got[i], want[i]) |
| 17 | + } |
| 18 | + } |
| 19 | +} |
| 20 | + |
| 21 | +func TestProjectBatchResultsByFields(t *testing.T) { |
| 22 | + batch := []any{ |
| 23 | + map[string]any{ |
| 24 | + "input": "q1", |
| 25 | + "ok": true, |
| 26 | + "results": []any{ |
| 27 | + map[string]any{"reference": "EP1000000A1", "title": "One"}, |
| 28 | + map[string]any{"reference": "EP1000001A1", "title": "Two"}, |
| 29 | + }, |
| 30 | + }, |
| 31 | + } |
| 32 | + projected, ok := projectBatchResultsByFields(batch, []string{"reference"}) |
| 33 | + if !ok { |
| 34 | + t.Fatal("expected batch projection to match") |
| 35 | + } |
| 36 | + rows := projected.([]map[string]any) |
| 37 | + if len(rows) != 1 { |
| 38 | + t.Fatalf("expected one batch row, got %d", len(rows)) |
| 39 | + } |
| 40 | + results, ok := rows[0]["results"].([]map[string]any) |
| 41 | + if !ok || len(results) != 2 { |
| 42 | + t.Fatalf("expected projected inner results, got %#v", rows[0]["results"]) |
| 43 | + } |
| 44 | + if results[0]["reference"] != "EP1000000A1" { |
| 45 | + t.Fatalf("unexpected projected value: %#v", results[0]) |
| 46 | + } |
| 47 | +} |
| 48 | + |
| 49 | +func TestProjectEnvelopeIfRequested(t *testing.T) { |
| 50 | + prev := flagPick |
| 51 | + flagPick = "quota.hourUsed,results.environments[0].dimensions[0].metrics[0].value" |
| 52 | + defer func() { flagPick = prev }() |
| 53 | + |
| 54 | + env := successEnvelope{ |
| 55 | + Quota: map[string]any{"hourUsed": 7}, |
| 56 | + Results: map[string]any{ |
| 57 | + "environments": []any{ |
| 58 | + map[string]any{ |
| 59 | + "dimensions": []any{ |
| 60 | + map[string]any{ |
| 61 | + "metrics": []any{ |
| 62 | + map[string]any{"value": 123}, |
| 63 | + }, |
| 64 | + }, |
| 65 | + }, |
| 66 | + }, |
| 67 | + }, |
| 68 | + }, |
| 69 | + } |
| 70 | + projected, ok := projectEnvelopeIfRequested(env) |
| 71 | + if !ok { |
| 72 | + t.Fatal("expected envelope projection to succeed") |
| 73 | + } |
| 74 | + row := projected.(map[string]any) |
| 75 | + if row["quota.hourUsed"] != 7 { |
| 76 | + t.Fatalf("unexpected quota projection: %#v", row) |
| 77 | + } |
| 78 | + if row["results.environments[0].dimensions[0].metrics[0].value"] != 123 { |
| 79 | + t.Fatalf("unexpected nested projection: %#v", row) |
| 80 | + } |
| 81 | +} |
| 82 | + |
| 83 | +func TestValidateCQLDateSyntax(t *testing.T) { |
| 84 | + if err := validateCQLDateSyntax(`pa=IBM and pd within "20250101 20251231"`); err != nil { |
| 85 | + t.Fatalf("unexpected validation failure: %v", err) |
| 86 | + } |
| 87 | + if err := validateCQLDateSyntax("pa=IBM and pd>=20250101"); err == nil { |
| 88 | + t.Fatal("expected invalid pd>=YYYYMMDD syntax error") |
| 89 | + } |
| 90 | +} |
| 91 | + |
| 92 | +func TestResolvePubInputFormatAndClaimsRouting(t *testing.T) { |
| 93 | + if got := resolvePubInputFormat("EP.1000000.A1", "auto"); got != "docdb" { |
| 94 | + t.Fatalf("unexpected auto format: %s", got) |
| 95 | + } |
| 96 | + if got := resolvePubInputFormat("EP1000000A1", "auto"); got != "epodoc" { |
| 97 | + t.Fatalf("unexpected auto format for epodoc reference: %s", got) |
| 98 | + } |
| 99 | + |
| 100 | + format, reference := routeClaimsAndDescriptionInput("epodoc", "EP1000000A1", "claims") |
| 101 | + if format != "docdb" || reference != "EP.1000000.A1" { |
| 102 | + t.Fatalf("unexpected claims routing: %s %s", format, reference) |
| 103 | + } |
| 104 | +} |
| 105 | + |
| 106 | +func TestNormalizeImageFetchPath(t *testing.T) { |
| 107 | + got := normalizeImageFetchPath("https://ops.epo.org/rest-services/published-data/images/EP/1000000/A1/fullimage") |
| 108 | + if got != "EP/1000000/A1/fullimage" { |
| 109 | + t.Fatalf("unexpected normalized link path: %s", got) |
| 110 | + } |
| 111 | +} |
| 112 | + |
| 113 | +func TestWithImageFetchPaths(t *testing.T) { |
| 114 | + input := map[string]any{ |
| 115 | + "document-instance": map[string]any{ |
| 116 | + "@link": "published-data/images/EP/1000000/A1/fullimage", |
| 117 | + }, |
| 118 | + } |
| 119 | + out := withImageFetchPaths(input).(map[string]any) |
| 120 | + di := asAnyMap(out["document-instance"]) |
| 121 | + if di["fetch_path"] != "EP/1000000/A1/fullimage" { |
| 122 | + t.Fatalf("expected fetch_path, got %#v", di) |
| 123 | + } |
| 124 | +} |
| 125 | + |
| 126 | +func TestWithFulltextSuggestions(t *testing.T) { |
| 127 | + body := []byte("<root><kind>A1</kind><kind>B1</kind></root>") |
| 128 | + out := withFulltextSuggestions("/published-data/publication/epodoc/EP1000000/fulltext", body, map[string]any{}).(map[string]any) |
| 129 | + commands, ok := out["suggested_retrieval_commands"].([]string) |
| 130 | + if !ok || len(commands) == 0 { |
| 131 | + t.Fatalf("expected suggested retrieval commands, got %#v", out["suggested_retrieval_commands"]) |
| 132 | + } |
| 133 | + if !strings.Contains(commands[0], "epo pub claims") { |
| 134 | + t.Fatalf("unexpected suggestion command: %s", commands[0]) |
| 135 | + } |
| 136 | +} |
| 137 | + |
| 138 | +func TestStripMixedLayoutNodes(t *testing.T) { |
| 139 | + input := map[string]any{ |
| 140 | + "reg:event-data": map[string]any{ |
| 141 | + "mixed.layout": []any{"one", "two"}, |
| 142 | + "kept": true, |
| 143 | + }, |
| 144 | + } |
| 145 | + out := stripMixedLayoutNodes(input).(map[string]any) |
| 146 | + eventData := asAnyMap(out["reg:event-data"]) |
| 147 | + if _, exists := eventData["mixed.layout"]; exists { |
| 148 | + t.Fatalf("mixed.layout should be stripped: %#v", eventData) |
| 149 | + } |
| 150 | +} |
| 151 | + |
| 152 | +func TestDetectNumberFormat(t *testing.T) { |
| 153 | + if got := detectNumberFormat("EP.1000000.A1"); got != "docdb" { |
| 154 | + t.Fatalf("unexpected format: %s", got) |
| 155 | + } |
| 156 | + if got := detectNumberFormat("EP1000000A1"); got != "epodoc" { |
| 157 | + t.Fatalf("unexpected format: %s", got) |
| 158 | + } |
| 159 | + if got := detectNumberFormat("US.(08/921,321).A.19970829"); got != "original" { |
| 160 | + t.Fatalf("unexpected format: %s", got) |
| 161 | + } |
| 162 | +} |
| 163 | + |
| 164 | +func TestFlattenLegalEvents(t *testing.T) { |
| 165 | + input := map[string]any{ |
| 166 | + "events": []any{ |
| 167 | + map[string]any{ |
| 168 | + "L001EP": "CODE", |
| 169 | + "L002EP": "Description", |
| 170 | + "L003EP": "DE", |
| 171 | + "L007EP": "20260101", |
| 172 | + }, |
| 173 | + }, |
| 174 | + } |
| 175 | + rows := flattenLegalEvents(input) |
| 176 | + if len(rows) != 1 { |
| 177 | + t.Fatalf("expected one legal row, got %d", len(rows)) |
| 178 | + } |
| 179 | + if rows[0]["code"] != "CODE" || rows[0]["country"] != "DE" { |
| 180 | + t.Fatalf("unexpected legal row: %#v", rows[0]) |
| 181 | + } |
| 182 | +} |
| 183 | + |
| 184 | +func TestSummarizeRegisterPayload(t *testing.T) { |
| 185 | + input := map[string]any{ |
| 186 | + "ops:world-patent-data": map[string]any{ |
| 187 | + "ops:register-search": map[string]any{ |
| 188 | + "reg:register-documents": map[string]any{ |
| 189 | + "reg:register-document": map[string]any{ |
| 190 | + "reg:bibliographic-data": map[string]any{ |
| 191 | + "reg:application-reference": map[string]any{ |
| 192 | + "reg:document-id": map[string]any{ |
| 193 | + "reg:country": map[string]any{"$": "EP"}, |
| 194 | + "reg:doc-number": map[string]any{"$": "123456"}, |
| 195 | + }, |
| 196 | + }, |
| 197 | + "reg:publication-reference": map[string]any{ |
| 198 | + "reg:document-id": map[string]any{ |
| 199 | + "reg:country": map[string]any{"$": "WO"}, |
| 200 | + "reg:doc-number": map[string]any{"$": "20260001"}, |
| 201 | + "reg:date": map[string]any{"$": "20260101"}, |
| 202 | + }, |
| 203 | + }, |
| 204 | + }, |
| 205 | + "reg:ep-patent-statuses": map[string]any{ |
| 206 | + "reg:ep-patent-status": map[string]any{"$": "Pending"}, |
| 207 | + }, |
| 208 | + }, |
| 209 | + }, |
| 210 | + }, |
| 211 | + "reg:designated-state": "DE", |
| 212 | + "reg:lapsed-in-country": "FR", |
| 213 | + }, |
| 214 | + } |
| 215 | + summary := summarizeRegisterPayload(input) |
| 216 | + if summary["status"] != "Pending" { |
| 217 | + t.Fatalf("unexpected register status: %#v", summary) |
| 218 | + } |
| 219 | + if summary["application"] != "EP123456" { |
| 220 | + t.Fatalf("unexpected application ref: %#v", summary) |
| 221 | + } |
| 222 | +} |
| 223 | + |
| 224 | +func TestExtractUsageRowsWithMetrics(t *testing.T) { |
| 225 | + input := map[string]any{ |
| 226 | + "environments": []any{ |
| 227 | + map[string]any{ |
| 228 | + "name": "prod", |
| 229 | + "dimensions": []any{ |
| 230 | + map[string]any{ |
| 231 | + "metrics": []any{ |
| 232 | + map[string]any{ |
| 233 | + "name": "message_count", |
| 234 | + "points": []any{ |
| 235 | + map[string]any{"date": "20260304", "value": 50}, |
| 236 | + }, |
| 237 | + }, |
| 238 | + map[string]any{ |
| 239 | + "name": "total_response_size", |
| 240 | + "points": []any{ |
| 241 | + map[string]any{"date": "20260304", "value": 4096}, |
| 242 | + }, |
| 243 | + }, |
| 244 | + }, |
| 245 | + }, |
| 246 | + }, |
| 247 | + }, |
| 248 | + }, |
| 249 | + } |
| 250 | + |
| 251 | + rows, ok := extractUsageRows(input) |
| 252 | + if !ok || len(rows) != 1 { |
| 253 | + t.Fatalf("expected flattened usage rows, got %#v", rows) |
| 254 | + } |
| 255 | + if rows[0]["message_count"] != "50" || rows[0]["total_response_size"] != "4096" { |
| 256 | + t.Fatalf("unexpected usage metrics row: %#v", rows[0]) |
| 257 | + } |
| 258 | +} |
| 259 | + |
| 260 | +func TestWithUsageHumanDates(t *testing.T) { |
| 261 | + input := map[string]any{"date": "20260304"} |
| 262 | + out := withUsageHumanDates(input).(map[string]any) |
| 263 | + if out["date_human"] == "" { |
| 264 | + t.Fatalf("expected human date enrichment: %#v", out) |
| 265 | + } |
| 266 | +} |
| 267 | + |
| 268 | +func TestNormalizeCPCPayload(t *testing.T) { |
| 269 | + searchXML := []byte(` |
| 270 | +<root> |
| 271 | + <classification-symbol>H04L45/00</classification-symbol> |
| 272 | + <class-title>Routing</class-title> |
| 273 | + <score>15.23</score> |
| 274 | +</root> |
| 275 | +`) |
| 276 | + searchRows := normalizeCPCPayload("search", "", "", "", searchXML) |
| 277 | + if len(searchRows) != 1 { |
| 278 | + t.Fatalf("expected one search row, got %d", len(searchRows)) |
| 279 | + } |
| 280 | + if searchRows[0]["symbol"] != "H04L45/00" { |
| 281 | + t.Fatalf("unexpected search row: %#v", searchRows[0]) |
| 282 | + } |
| 283 | + |
| 284 | + mapXML := []byte(`<root><classification-symbol>H04L45/00</classification-symbol><classification-symbol>H04L45/10</classification-symbol></root>`) |
| 285 | + mapRows := normalizeCPCPayload("map", "H04L45/00", "cpc", "ipc", mapXML) |
| 286 | + if len(mapRows) == 0 { |
| 287 | + t.Fatal("expected mapping rows") |
| 288 | + } |
| 289 | + if mapRows[0]["fromScheme"] != "CPC" || mapRows[0]["toScheme"] != "IPC" { |
| 290 | + t.Fatalf("unexpected map row: %#v", mapRows[0]) |
| 291 | + } |
| 292 | +} |
0 commit comments