github.com/letsencrypt/boulder@v0.20251208.0/test/zendeskfake/zendeskfake_test.go (about) 1 package zendeskfake 2 3 import ( 4 "bytes" 5 "encoding/base64" 6 "encoding/json" 7 "fmt" 8 "io" 9 "net/http" 10 "net/http/httptest" 11 "net/url" 12 "strconv" 13 "strings" 14 "testing" 15 ) 16 17 const ( 18 apiTokenEmail = "tester@example.com" 19 apiToken = "someToken" 20 ) 21 22 func basicAuthHeader(email, token string) string { 23 raw := email + "/token:" + token 24 enc := base64.StdEncoding.EncodeToString([]byte(raw)) 25 return "Basic " + enc 26 } 27 28 func startTestServer(t *testing.T) (*Server, *httptest.Server) { 29 t.Helper() 30 31 srv := NewServer(apiTokenEmail, apiToken, nil) 32 ts := httptest.NewServer(srv.Handler()) 33 t.Cleanup(ts.Close) 34 return srv, ts 35 } 36 37 func startTestServerWithStore(t *testing.T, store *Store) (*Server, *httptest.Server) { 38 t.Helper() 39 40 srv := NewServer(apiTokenEmail, apiToken, store) 41 ts := httptest.NewServer(srv.Handler()) 42 t.Cleanup(ts.Close) 43 return srv, ts 44 } 45 46 func doJSON(t *testing.T, method, urlStr, authHeader string, body []byte, setContentType bool) (*http.Response, []byte) { 47 t.Helper() 48 49 var reader io.Reader 50 if len(body) > 0 { 51 reader = bytes.NewReader(body) 52 } 53 54 req, err := http.NewRequest(method, urlStr, reader) 55 if err != nil { 56 t.Errorf("creating request %s %s failed: %s", method, urlStr, err) 57 return nil, nil 58 } 59 if authHeader != "" { 60 req.Header.Set("Authorization", authHeader) 61 } 62 req.Header.Set("Accept", "application/json") 63 if setContentType { 64 req.Header.Set("Content-Type", "application/json") 65 } 66 67 resp, err := http.DefaultClient.Do(req) 68 if err != nil { 69 t.Errorf("performing %s %s failed: %s", method, urlStr, err) 70 return nil, nil 71 } 72 73 respBody, err := io.ReadAll(resp.Body) 74 if err != nil { 75 t.Errorf("reading response body for %s %s failed: %s", method, urlStr, err) 76 err = resp.Body.Close() 77 if err != nil { 78 t.Errorf("closing response body for %s %s failed: %s", method, urlStr, err) 79 } 80 return resp, nil 81 } 82 err = resp.Body.Close() 83 if err != nil { 84 t.Errorf("closing response body for %s %s failed: %s", method, urlStr, err) 85 } 86 return resp, respBody 87 } 88 89 func postTicket(t *testing.T, baseURL string, body []byte) (*http.Response, []byte) { 90 t.Helper() 91 92 return doJSON(t, http.MethodPost, baseURL+TicketsJSONPath, basicAuthHeader(apiTokenEmail, apiToken), body, true) 93 } 94 95 func putUpdate(t *testing.T, baseURL string, id int64, body []byte) (*http.Response, []byte) { 96 t.Helper() 97 98 endpoint := fmt.Sprintf("%s%s%d.json", baseURL, TicketsPath, id) 99 return doJSON(t, http.MethodPut, endpoint, basicAuthHeader(apiTokenEmail, apiToken), body, true) 100 } 101 102 func getSearch(t *testing.T, baseURL, query string) (*http.Response, []byte) { 103 t.Helper() 104 105 v := url.Values{} 106 v.Set("query", query) 107 urlStr := baseURL + SearchJSONPath + "?" + v.Encode() 108 return doJSON(t, http.MethodGet, urlStr, basicAuthHeader(apiTokenEmail, apiToken), nil, false) 109 } 110 111 func createTicketAndReturnID(t *testing.T, baseURL string) int64 { 112 t.Helper() 113 114 payload := []byte(`{ 115 "ticket": { 116 "requester": {"name":"R","email":"r@example.com"}, 117 "subject": "S", 118 "comment": {"body":"B","public":true}, 119 "custom_fields": [] 120 } 121 }`) 122 resp, body := postTicket(t, baseURL, payload) 123 if resp == nil { 124 t.Errorf("unexpected nil response while creating ticket") 125 return 0 126 } 127 if resp.StatusCode != http.StatusCreated { 128 t.Errorf("create ticket: expected HTTP %d, got HTTP %d body=%s", http.StatusCreated, resp.StatusCode, string(body)) 129 } 130 131 var out struct { 132 Ticket struct { 133 ID int64 `json:"id"` 134 } `json:"ticket"` 135 } 136 err := json.Unmarshal(body, &out) 137 if err != nil { 138 t.Errorf("unmarshalling create ticket response failed: %s", err) 139 return 0 140 } 141 return out.Ticket.ID 142 } 143 144 func TestAuthRequired(t *testing.T) { 145 t.Parallel() 146 147 _, ts := startTestServer(t) 148 149 resp, _ := doJSON(t, http.MethodGet, ts.URL+SearchJSONPath+"?query=type:ticket", "", nil, false) 150 if resp == nil { 151 t.Errorf("unexpected nil response for unauthorized request") 152 return 153 } 154 if resp.StatusCode != http.StatusUnauthorized { 155 t.Errorf("unauthorized request: expected HTTP %d, got HTTP %d", http.StatusUnauthorized, resp.StatusCode) 156 } 157 } 158 159 func TestAuthWrongCredentialsAllEndpoints(t *testing.T) { 160 t.Parallel() 161 162 _, ts := startTestServer(t) 163 164 validCreate := []byte(`{"ticket":{"requester":{"name":"n","email":"e@example.com"},"subject":"s","comment":{"body":"b","public":true},"custom_fields":[]}}`) 165 validUpdate := []byte(`{"ticket":{"comment":{"body":"x","public":false}}}`) 166 167 id := createTicketAndReturnID(t, ts.URL) 168 169 type ep struct { 170 name string 171 method string 172 url string 173 body []byte 174 } 175 endpoints := []ep{ 176 {"POST /tickets.json", http.MethodPost, ts.URL + TicketsJSONPath, validCreate}, 177 {"GET /search.json", http.MethodGet, ts.URL + SearchJSONPath + "?query=type:ticket", nil}, 178 {"PUT /tickets/{id}.json", http.MethodPut, ts.URL + TicketsPath + strconv.FormatInt(id, 10) + ".json", validUpdate}, 179 } 180 181 for _, e := range endpoints { 182 t.Run(e.name+"/wrong-credentials", func(t *testing.T) { 183 resp, _ := doJSON(t, e.method, e.url, basicAuthHeader("wrong@example.com", "wrong"), e.body, true) 184 if resp == nil { 185 t.Errorf("%s wrong-credentials: unexpected nil response", e.name) 186 return 187 } 188 if resp.StatusCode != http.StatusUnauthorized { 189 t.Errorf("%s wrong-credentials: expected HTTP %d, got HTTP %d", e.name, http.StatusUnauthorized, resp.StatusCode) 190 } 191 }) 192 t.Run(e.name+"/malformed-header", func(t *testing.T) { 193 resp, _ := doJSON(t, e.method, e.url, "Basic malformed-header", e.body, true) 194 if resp == nil { 195 t.Errorf("%s malformed-header: unexpected nil response", e.name) 196 return 197 } 198 if resp.StatusCode != http.StatusUnauthorized { 199 t.Errorf("%s malformed-header: expected HTTP %d, got HTTP %d", e.name, http.StatusUnauthorized, resp.StatusCode) 200 } 201 }) 202 } 203 } 204 205 func TestCreateTicketSuccessAndStored(t *testing.T) { 206 t.Parallel() 207 208 srv, ts := startTestServer(t) 209 210 payload := []byte(`{ 211 "ticket": { 212 "requester": {"name":"Alice","email":"alice@example.com"}, 213 "subject": "Subject A", 214 "comment": {"body":"Hello world","public":true}, 215 "custom_fields": [ 216 {"id": 111, "value":"pending"}, 217 {"id": 222, "value":"Acme"} 218 ] 219 } 220 }`) 221 222 resp, body := postTicket(t, ts.URL, payload) 223 if resp == nil { 224 t.Errorf("create ticket: unexpected nil response") 225 return 226 } 227 if resp.StatusCode != http.StatusCreated { 228 t.Errorf("create ticket: expected HTTP %d, got HTTP %d body=%s", http.StatusCreated, resp.StatusCode, string(body)) 229 } 230 231 var res struct { 232 Ticket struct { 233 ID int64 `json:"id"` 234 } `json:"ticket"` 235 } 236 err := json.Unmarshal(body, &res) 237 if err != nil { 238 t.Errorf("unmarshal create response failed: %s", err) 239 return 240 } 241 if res.Ticket.ID == 0 { 242 t.Errorf("create ticket: expected non-zero id") 243 return 244 } 245 246 got, ok := srv.GetTicket(res.Ticket.ID) 247 if !ok { 248 t.Errorf("ticket id %d not found in store", res.Ticket.ID) 249 return 250 } 251 if got.Status != "new" { 252 t.Errorf("ticket default status mismatch: got %q, want %q", got.Status, "new") 253 } 254 if got.Subject != "Subject A" { 255 t.Errorf("ticket subject mismatch: got %q, want %q", got.Subject, "Subject A") 256 } 257 if len(got.Comments) != 1 || got.Comments[0].Body != "Hello world" || !got.Comments[0].Public { 258 t.Errorf("ticket comment stored incorrectly: %#v (want one public 'Hello world' comment)", got.Comments) 259 } 260 if got.CustomFields[111] != "pending" || got.CustomFields[222] != "Acme" { 261 t.Errorf("ticket custom fields stored incorrectly: %#v (want 111=%q 222=%q)", got.CustomFields, "pending", "Acme") 262 } 263 } 264 265 func TestCreateTicketUnhappyPaths(t *testing.T) { 266 t.Parallel() 267 268 _, ts := startTestServer(t) 269 270 cases := []struct { 271 name string 272 body []byte 273 wantStatus int 274 }{ 275 { 276 name: "bad json", 277 body: []byte(`{"ticket": { "requester": {"name":"A","email":"a@example.com"},`), 278 wantStatus: http.StatusBadRequest, 279 }, 280 { 281 name: "missing subject", 282 body: []byte(`{ 283 "ticket": { 284 "requester": {"name":"Bob","email":"bob@example.com"}, 285 "comment": {"body":"Hi","public":true} 286 } 287 }`), 288 wantStatus: http.StatusUnprocessableEntity, 289 }, 290 { 291 name: "missing email", 292 body: []byte(`{ 293 "ticket": { 294 "requester": {"name":"NoEmail"}, 295 "subject": "S", 296 "comment": {"body":"B","public":true} 297 } 298 }`), 299 wantStatus: http.StatusUnprocessableEntity, 300 }, 301 { 302 name: "missing comment body", 303 body: []byte(`{ 304 "ticket": { 305 "requester": {"name":"N","email":"n@example.com"}, 306 "subject": "S", 307 "comment": {"public":true} 308 } 309 }`), 310 wantStatus: http.StatusUnprocessableEntity, 311 }, 312 { 313 name: "empty body", 314 body: nil, 315 wantStatus: http.StatusBadRequest, 316 }, 317 } 318 319 for _, tc := range cases { 320 t.Run(tc.name, func(t *testing.T) { 321 t.Parallel() 322 323 resp, body := postTicket(t, ts.URL, tc.body) 324 if resp == nil { 325 t.Errorf("create ticket (%s): unexpected nil response", tc.name) 326 return 327 } 328 if resp.StatusCode != tc.wantStatus { 329 t.Errorf("create ticket (%s): expected HTTP %d, got HTTP %d body=%s", tc.name, tc.wantStatus, resp.StatusCode, string(body)) 330 } 331 }) 332 } 333 } 334 335 func TestUpdateTicketVariants(t *testing.T) { 336 t.Parallel() 337 338 type tc struct { 339 name string 340 payload []byte 341 wantHTTP int 342 expectStatus string 343 expectComment *comment 344 } 345 346 cases := []tc{ 347 { 348 name: "adds comment only", 349 payload: []byte(`{"ticket":{"comment":{"body":"Follow-up","public":false}}}`), 350 wantHTTP: http.StatusOK, 351 expectStatus: "new", 352 expectComment: &comment{Body: "Follow-up", Public: false}, 353 }, 354 { 355 name: "status only (open)", 356 payload: []byte(`{"ticket":{"status":"open"}}`), 357 wantHTTP: http.StatusOK, 358 expectStatus: "open", 359 }, 360 { 361 name: "status with comment (solved, public)", 362 payload: []byte(`{"ticket":{"status":"solved","comment":{"body":"Resolved","public":true}}}`), 363 wantHTTP: http.StatusOK, 364 expectStatus: "solved", 365 expectComment: &comment{Body: "Resolved", Public: true}, 366 }, 367 { 368 name: "invalid status", 369 payload: []byte(`{"ticket":{"status":"bogus"}}`), 370 wantHTTP: http.StatusUnprocessableEntity, 371 expectStatus: "new", 372 }, 373 } 374 375 for _, tc := range cases { 376 t.Run(tc.name, func(t *testing.T) { 377 t.Parallel() 378 379 srv, ts := startTestServer(t) 380 id := createTicketAndReturnID(t, ts.URL) 381 382 resp, body := putUpdate(t, ts.URL, id, tc.payload) 383 if resp == nil { 384 t.Fatalf("unexpected nil response from putUpdate %s", tc.name) 385 } 386 if resp.StatusCode != tc.wantHTTP { 387 t.Errorf("expected HTTP %d, got %d body=%s", tc.wantHTTP, resp.StatusCode, string(body)) 388 } 389 390 got, ok := srv.GetTicket(id) 391 if !ok { 392 t.Errorf("id %d not found in store after update %s", id, tc.name) 393 } 394 395 if got.Status != tc.expectStatus { 396 t.Errorf("status mismatch: got %q, expected %q", got.Status, tc.expectStatus) 397 } 398 if tc.expectComment != nil { 399 found := false 400 for _, c := range got.Comments { 401 if c.Body == tc.expectComment.Body && c.Public == tc.expectComment.Public { 402 found = true 403 break 404 } 405 } 406 if !found { 407 t.Errorf("expected comment %q public=%t not found in ticket comments: %#v", tc.expectComment.Body, tc.expectComment.Public, got.Comments) 408 } 409 } else if len(got.Comments) > 1 { 410 t.Errorf("expected no additional comments, got %d comments: %#v", len(got.Comments), got.Comments) 411 } 412 }) 413 } 414 } 415 416 func TestUpdateTicketUnhappyPaths(t *testing.T) { 417 t.Parallel() 418 419 _, ts := startTestServer(t) 420 421 validID := createTicketAndReturnID(t, ts.URL) 422 423 type tc struct { 424 name string 425 method string 426 path string 427 body []byte 428 wantStatus int 429 } 430 tests := []tc{ 431 { 432 name: "bad id path (non-numeric)", 433 method: http.MethodPut, 434 path: TicketsPath + "abc.json", 435 body: []byte(`{"ticket":{"comment":{"body":"x","public":false}}}`), 436 wantStatus: http.StatusNotFound, 437 }, 438 { 439 name: "missing id segment", 440 method: http.MethodPut, 441 path: TicketsPath + ".json", 442 body: []byte(`{"ticket":{"comment":{"body":"x","public":true}}}`), 443 wantStatus: http.StatusNotFound, 444 }, 445 { 446 name: "unknown id", 447 method: http.MethodPut, 448 path: TicketsPath + "999999.json", 449 body: []byte(`{"ticket":{"comment":{"body":"x","public":true}}}`), 450 wantStatus: http.StatusNotFound, 451 }, 452 { 453 name: "bad json", 454 method: http.MethodPut, 455 path: TicketsPath + strconv.FormatInt(validID, 10) + ".json", 456 body: []byte(`{"ticket": {"comment":`), 457 wantStatus: http.StatusBadRequest, 458 }, 459 { 460 name: "missing comment body", 461 method: http.MethodPut, 462 path: TicketsPath + strconv.FormatInt(validID, 10) + ".json", 463 body: []byte(`{"ticket":{"comment":{"public":true}}}`), 464 wantStatus: http.StatusUnprocessableEntity, 465 }, 466 } 467 468 for _, tt := range tests { 469 t.Run(tt.name, func(t *testing.T) { 470 t.Parallel() 471 472 resp, body := doJSON(t, tt.method, ts.URL+tt.path, basicAuthHeader(apiTokenEmail, apiToken), tt.body, true) 473 if resp == nil { 474 t.Errorf("%s: unexpected nil response", tt.name) 475 return 476 } 477 if resp.StatusCode != tt.wantStatus { 478 t.Errorf("%s: expected HTTP %d, got HTTP %d body=%s", tt.name, tt.wantStatus, resp.StatusCode, string(body)) 479 } 480 481 if tt.wantStatus == http.StatusNotFound && (strings.Contains(tt.name, "bad id path") || strings.Contains(tt.name, "missing id")) { 482 ct := resp.Header.Get("Content-Type") 483 if !strings.HasPrefix(ct, "application/json") { 484 t.Errorf("%s: expected Content-Type application/json, got %q", tt.name, ct) 485 } 486 var payload struct { 487 Error string `json:"error"` 488 Description string `json:"description"` 489 } 490 err := json.Unmarshal(body, &payload) 491 if err != nil { 492 t.Errorf("%s: unmarshal 404 payload failed: %s (body=%q)", tt.name, err, string(body)) 493 } 494 if payload.Error != "RecordNotFound" || payload.Description != "Not found" { 495 t.Errorf("%s: unexpected 404 payload: %#v", tt.name, payload) 496 } 497 } 498 }) 499 } 500 } 501 502 func TestSearchNoTypeTicketReturnsEmpty(t *testing.T) { 503 t.Parallel() 504 505 _, ts := startTestServer(t) 506 507 resp, body := getSearch(t, ts.URL, "custom_field_1:foo") 508 if resp == nil { 509 t.Errorf("search without type: unexpected nil response") 510 return 511 } 512 if resp.StatusCode != http.StatusOK { 513 t.Errorf("search without type: expected HTTP %d, got HTTP %d", http.StatusOK, resp.StatusCode) 514 } 515 516 var out struct { 517 Results []any `json:"results"` 518 Next any `json:"next_page"` 519 } 520 err := json.Unmarshal(body, &out) 521 if err != nil { 522 t.Errorf("search without type: unmarshal response failed: %s", err) 523 } 524 if len(out.Results) != 0 { 525 t.Errorf("search without type: expected 0 results, got %d", len(out.Results)) 526 } 527 } 528 529 func TestSearchByCustomFieldsQuotedAndUnquoted(t *testing.T) { 530 t.Parallel() 531 532 _, ts := startTestServer(t) 533 534 payload1 := []byte(`{ 535 "ticket": { 536 "requester": {"name":"A","email":"a@example.com"}, 537 "subject": "S1", 538 "comment": {"body":"B1","public":true}, 539 "custom_fields": [ 540 {"id": 111, "value": "pending"}, 541 {"id": 222, "value": "Acme"} 542 ] 543 } 544 }`) 545 resp, body := postTicket(t, ts.URL, payload1) 546 if resp == nil { 547 t.Errorf("create ticket 1: unexpected nil response") 548 return 549 } 550 if resp.StatusCode != http.StatusCreated { 551 t.Errorf("create ticket 1: expected HTTP %d, got HTTP %d body=%s", http.StatusCreated, resp.StatusCode, string(body)) 552 } 553 554 payload2 := []byte(`{ 555 "ticket": { 556 "requester": {"name":"B","email":"b@example.com"}, 557 "subject": "S2", 558 "comment": {"body":"B2","public":true}, 559 "custom_fields": [ 560 {"id": 111, "value": "pending review"}, 561 {"id": 222, "value": "Acme"} 562 ] 563 } 564 }`) 565 resp, body = postTicket(t, ts.URL, payload2) 566 if resp == nil { 567 t.Errorf("create ticket 2: unexpected nil response") 568 return 569 } 570 if resp.StatusCode != http.StatusCreated { 571 t.Errorf("create ticket 2: expected HTTP %d, got HTTP %d body=%s", http.StatusCreated, resp.StatusCode, string(body)) 572 } 573 574 resp, body = getSearch(t, ts.URL, `type:ticket custom_field_111:pending`) 575 if resp == nil { 576 t.Errorf("search unquoted: unexpected nil response") 577 return 578 } 579 if resp.StatusCode != http.StatusOK { 580 t.Errorf("search unquoted: expected HTTP %d, got HTTP %d", http.StatusOK, resp.StatusCode) 581 } 582 var res1 struct{ Results []any } 583 err := json.Unmarshal(body, &res1) 584 if err != nil { 585 t.Errorf("search unquoted: unmarshal failed: %s", err) 586 } 587 if len(res1.Results) != 1 { 588 t.Errorf("search unquoted: expected 1 result, got %d", len(res1.Results)) 589 } 590 591 resp, body = getSearch(t, ts.URL, `type:ticket custom_field_111:"pending review"`) 592 if resp == nil { 593 t.Errorf("search quoted: unexpected nil response") 594 return 595 } 596 if resp.StatusCode != http.StatusOK { 597 t.Errorf("search quoted: expected HTTP %d, got HTTP %d", http.StatusOK, resp.StatusCode) 598 } 599 var res2 struct{ Results []any } 600 err = json.Unmarshal(body, &res2) 601 if err != nil { 602 t.Errorf("search quoted: unmarshal failed: %s", err) 603 } 604 if len(res2.Results) != 1 { 605 t.Errorf("search quoted: expected 1 result, got %d", len(res2.Results)) 606 } 607 } 608 609 func TestSearchNewestFirstOrder(t *testing.T) { 610 t.Parallel() 611 612 _, ts := startTestServer(t) 613 614 for i := 1; i <= 3; i++ { 615 payload := fmt.Sprintf(`{ 616 "ticket": { 617 "requester": {"name":"U%d","email":"u%d@example.com"}, 618 "subject": "S%d", 619 "comment": {"body":"B%d","public":true}, 620 "custom_fields": [ 621 {"id": 999, "value": "x"} 622 ] 623 } 624 }`, i, i, i, i) 625 626 resp, body := postTicket(t, ts.URL, []byte(payload)) 627 if resp == nil { 628 t.Errorf("create ticket %d: unexpected nil response", i) 629 return 630 } 631 if resp.StatusCode != http.StatusCreated { 632 t.Errorf("create ticket %d: expected HTTP %d, got HTTP %d body=%s", i, http.StatusCreated, resp.StatusCode, string(body)) 633 return 634 } 635 } 636 637 type item struct { 638 ID int64 `json:"id"` 639 } 640 type page struct { 641 Results []item `json:"results"` 642 Next *string `json:"next_page"` 643 } 644 645 var all []item 646 647 resp, body := getSearch(t, ts.URL, `type:ticket custom_field_999:x`) 648 if resp == nil { 649 t.Errorf("initial search: unexpected nil response") 650 return 651 } 652 if resp.StatusCode != http.StatusOK { 653 t.Errorf("initial search: expected HTTP %d, got HTTP %d", http.StatusOK, resp.StatusCode) 654 return 655 } 656 var pg page 657 err := json.Unmarshal(body, &pg) 658 if err != nil { 659 t.Errorf("initial search: unmarshal failed: %s", err) 660 return 661 } 662 all = append(all, pg.Results...) 663 664 next := pg.Next 665 for next != nil && *next != "" { 666 nextURL := *next 667 if strings.HasPrefix(nextURL, "/") { 668 nextURL = ts.URL + nextURL 669 } 670 resp, body = doJSON(t, http.MethodGet, nextURL, basicAuthHeader(apiTokenEmail, apiToken), nil, false) 671 if resp == nil { 672 t.Errorf("paginated search: unexpected nil response on next_page") 673 return 674 } 675 if resp.StatusCode != http.StatusOK { 676 t.Errorf("paginated search: expected HTTP %d on next_page, got HTTP %d", http.StatusOK, resp.StatusCode) 677 return 678 } 679 var np page 680 err = json.Unmarshal(body, &np) 681 if err != nil { 682 t.Errorf("paginated search: unmarshal next_page failed: %s", err) 683 return 684 } 685 all = append(all, np.Results...) 686 next = np.Next 687 } 688 689 if len(all) != 3 { 690 t.Errorf("expected 3 results, got %d", len(all)) 691 return 692 } 693 if !(all[0].ID > all[1].ID && all[1].ID > all[2].ID) { 694 t.Errorf("order incorrect (want strictly descending IDs): %#v", all) 695 } 696 } 697 698 func TestCapacityEviction(t *testing.T) { 699 t.Parallel() 700 701 store := NewStore(2) 702 srv, ts := startTestServerWithStore(t, store) 703 704 for i := 1; i <= 3; i++ { 705 payload := fmt.Sprintf(`{ 706 "ticket": { 707 "requester": {"name":"E%d","email":"e%d@example.com"}, 708 "subject": "Sub%d", 709 "comment": {"body":"C%d","public":true}, 710 "custom_fields": [ 711 {"id": 111, "value": "v%d"} 712 ] 713 } 714 }`, i, i, i, i, i) 715 716 resp, body := postTicket(t, ts.URL, []byte(payload)) 717 if resp == nil { 718 t.Errorf("unexpected nil response creating ticket %d", i) 719 return 720 } 721 if resp.StatusCode != http.StatusCreated { 722 t.Errorf("create ticket %d expected HTTP %d, got HTTP %d body=%s", i, http.StatusCreated, resp.StatusCode, string(body)) 723 } 724 } 725 726 _, ok := srv.GetTicket(1) 727 if ok { 728 t.Errorf("expected ticket 1 to be evicted") 729 } 730 _, ok = srv.GetTicket(2) 731 if !ok { 732 t.Errorf("expected ticket 2 to remain") 733 } 734 _, ok = srv.GetTicket(3) 735 if !ok { 736 t.Errorf("expected ticket 3 to remain") 737 } 738 } 739 740 func TestSearchInvalidCustomFieldID(t *testing.T) { 741 t.Parallel() 742 743 _, ts := startTestServer(t) 744 _ = createTicketAndReturnID(t, ts.URL) 745 746 resp, body := getSearch(t, ts.URL, `type:ticket custom_field_abc:foo`) 747 if resp == nil { 748 t.Errorf("invalid custom field id search: unexpected nil response") 749 return 750 } 751 if resp.StatusCode != http.StatusBadRequest { 752 t.Errorf("invalid custom field id search: expected HTTP %d, got HTTP %d body=%s", http.StatusBadRequest, resp.StatusCode, string(body)) 753 } 754 } 755 756 func TestSearchMissingQueryParam(t *testing.T) { 757 t.Parallel() 758 759 _, ts := startTestServer(t) 760 761 resp, body := doJSON(t, http.MethodGet, ts.URL+SearchJSONPath, basicAuthHeader(apiTokenEmail, apiToken), nil, false) 762 if resp == nil { 763 t.Errorf("missing query param search: unexpected nil response") 764 return 765 } 766 if resp.StatusCode != http.StatusOK { 767 t.Errorf("missing query param search: expected HTTP %d, got HTTP %d", http.StatusOK, resp.StatusCode) 768 } 769 770 var out struct { 771 Results []any `json:"results"` 772 Next any `json:"next_page"` 773 } 774 err := json.Unmarshal(body, &out) 775 if err != nil { 776 t.Errorf("missing query param search: unmarshal failed: %s", err) 777 } 778 if len(out.Results) != 0 { 779 t.Errorf("missing query param search: expected 0 results, got %d", len(out.Results)) 780 } 781 }