File/package naming convention conforming better to go standards - as ugly as it is
This commit is contained in:
1 parent
be4770cbab
commit
d6688a6754
32 files changed
+36
-36
No files matched your search
@@ -0,0 +1,209 @@
|
||||
package mock
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.avsdev.uk/Go/job-search/internal/mock/findatender/ocds"
|
||||
)
|
||||
|
||||
func TestUpdatedFromIsLocalTime(t *testing.T) {
|
||||
london, err := time.LoadLocation("Europe/London")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
store, err := OpenStore(filepath.Join(t.TempDir(), "r.jsonl"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer store.Close()
|
||||
|
||||
// Two releases at 11:45Z and 12:15Z, i.e. 12:45 and 13:15 BST.
|
||||
for i, at := range []time.Time{
|
||||
time.Date(2026, 10, 8, 11, 45, 0, 0, time.UTC),
|
||||
time.Date(2026, 10, 8, 12, 15, 0, 0, time.UTC),
|
||||
} {
|
||||
if err := store.Append(
|
||||
ocds.Release{
|
||||
OCID: "ocds-test",
|
||||
ID: []string{"000001-2026", "000002-2026"}[i],
|
||||
Date: at,
|
||||
Tag: []string{"tender"},
|
||||
},
|
||||
); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
h := NewHandler(store, "", london)
|
||||
count := func(query string) int {
|
||||
rec := httptest.NewRecorder()
|
||||
h(rec, httptest.NewRequest("GET", BasePath+"?"+query, nil))
|
||||
var pkg ocds.ReleasePackage
|
||||
if err := json.Unmarshal(rec.Body.Bytes(), &pkg); err != nil {
|
||||
t.Fatalf("%s: %v (%s)", query, err, rec.Body.String())
|
||||
}
|
||||
return len(pkg.Releases)
|
||||
}
|
||||
|
||||
cases := map[string]int{
|
||||
"updatedFrom=2026-10-08T13:00:00": 1, // 13:00 BST = 12:00Z
|
||||
"updatedFrom=2026-10-08T12:30:00": 2, // 12:30 BST = 11:30Z
|
||||
"updatedFrom=2026-10-08T12:00:00Z": 1, // explicit UTC still honoured
|
||||
"updatedFrom=2026-10-08T12:00:00%2B01:00": 2, // explicit offset still honoured
|
||||
"updatedFrom=2026-10-08T12:00:00&updatedTo=2026-10-08T13:00:00": 1, // window in BST
|
||||
}
|
||||
for q, want := range cases {
|
||||
if got := count(q); got != want {
|
||||
t.Errorf("%s: got %d releases, want %d", q, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// In winter (GMT) London local time equals UTC.
|
||||
winter := time.Date(2026, 12, 1, 9, 0, 0, 0, time.UTC)
|
||||
if got, _ := parseTime("2026-12-01T09:00:00", london); !got.Equal(winter) {
|
||||
t.Errorf("GMT: got %s, want %s", got, winter)
|
||||
}
|
||||
}
|
||||
|
||||
func openTestStore(t *testing.T) *Store {
|
||||
t.Helper()
|
||||
store, err := OpenStore(filepath.Join(t.TempDir(), "r.jsonl"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { store.Close() })
|
||||
return store
|
||||
}
|
||||
|
||||
func TestHandlerRejectsBadRequests(t *testing.T) {
|
||||
h := NewHandler(openTestStore(t), "", nil)
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
h(rec, httptest.NewRequest("POST", BasePath, nil))
|
||||
if rec.Code != http.StatusMethodNotAllowed {
|
||||
t.Errorf("POST: status %d, want 405", rec.Code)
|
||||
}
|
||||
|
||||
cursor := func(s string) string { return base64.RawURLEncoding.EncodeToString([]byte(s)) }
|
||||
cases := map[string]string{
|
||||
"limit=0": "limit must be an integer between 1 and 100",
|
||||
"limit=101": "limit must be an integer between 1 and 100",
|
||||
"limit=ten": "limit must be an integer between 1 and 100",
|
||||
"updatedFrom=yesterday": "updatedFrom must be in format yyyy-MM-ddTHH:mm:ss",
|
||||
"updatedTo=2026-13-01": "updatedTo must be in format yyyy-MM-ddTHH:mm:ss",
|
||||
"updatedFrom=2026-10-08T12:00:00&updatedTo=2026-10-08T11:00:00": "updatedTo must not be before updatedFrom",
|
||||
"cursor=!!!": "invalid cursor",
|
||||
"cursor=" + cursor("pos:1"): "invalid cursor",
|
||||
"cursor=" + cursor("idx:one"): "invalid cursor",
|
||||
"cursor=" + cursor("idx:-1"): "invalid cursor",
|
||||
}
|
||||
for q, want := range cases {
|
||||
rec := httptest.NewRecorder()
|
||||
h(rec, httptest.NewRequest("GET", BasePath+"?"+q, nil))
|
||||
if rec.Code != http.StatusBadRequest {
|
||||
t.Errorf("%s: status %d, want 400", q, rec.Code)
|
||||
continue
|
||||
}
|
||||
if ct := rec.Header().Get("Content-Type"); ct != "application/json" {
|
||||
t.Errorf("%s: Content-Type %q", q, ct)
|
||||
}
|
||||
var body map[string]string
|
||||
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil || body["error"] != want {
|
||||
t.Errorf("%s: body %s, want error %q", q, rec.Body.String(), want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCursorRoundTrip(t *testing.T) {
|
||||
for _, i := range []int{0, 1, 99, 123456} {
|
||||
if got, err := decodeCursor(encodeCursor(i)); err != nil || got != i {
|
||||
t.Errorf("decodeCursor(encodeCursor(%d)) = %d, %v", i, got, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseTimeFormats(t *testing.T) {
|
||||
cases := map[string]time.Time{
|
||||
"2026-10-08T12:00:00Z": time.Date(2026, 10, 8, 12, 0, 0, 0, time.UTC),
|
||||
"2026-10-08T12:00:00+02:00": time.Date(2026, 10, 8, 10, 0, 0, 0, time.UTC),
|
||||
"2026-10-08T12:00:00": time.Date(2026, 10, 8, 12, 0, 0, 0, time.UTC),
|
||||
"2026-10-08": time.Date(2026, 10, 8, 0, 0, 0, 0, time.UTC),
|
||||
}
|
||||
for v, want := range cases {
|
||||
got, err := parseTime(v, nil) // nil location means UTC
|
||||
if err != nil || !got.Equal(want) {
|
||||
t.Errorf("parseTime(%q) = %s, %v; want %s", v, got, err, want)
|
||||
}
|
||||
}
|
||||
for _, v := range []string{"", "08/10/2026", "2026-10-08 12:00:00", "2026-10-08T25:00:00"} {
|
||||
if _, err := parseTime(v, nil); err == nil {
|
||||
t.Errorf("parseTime(%q): expected error", v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Following links.next walks every release exactly once, keeping the other
|
||||
// query parameters, and the last page has no next link.
|
||||
func TestHandlerPagination(t *testing.T) {
|
||||
store := openTestStore(t)
|
||||
base := time.Date(2026, 10, 8, 9, 0, 0, 0, time.UTC)
|
||||
for i := 0; i < 5; i++ {
|
||||
r := ocds.Release{
|
||||
OCID: fmt.Sprintf("ocds-test-%d", i),
|
||||
ID: fmt.Sprintf("%06d-2026", i+1),
|
||||
Date: base.Add(time.Duration(i) * time.Minute),
|
||||
Tag: []string{"tender"},
|
||||
}
|
||||
if err := store.Append(r); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
const baseURL = "http://mock.test"
|
||||
h := NewHandler(store, baseURL, nil)
|
||||
next := BasePath + "?limit=2&updatedFrom=2026-10-08T09:01:00"
|
||||
var ids []string
|
||||
for pages := 0; next != ""; pages++ {
|
||||
if pages > 5 {
|
||||
t.Fatal("too many pages")
|
||||
}
|
||||
rec := httptest.NewRecorder()
|
||||
h(rec, httptest.NewRequest("GET", next, nil))
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("%s: status %d: %s", next, rec.Code, rec.Body.String())
|
||||
}
|
||||
var pkg ocds.ReleasePackage
|
||||
if err := json.Unmarshal(rec.Body.Bytes(), &pkg); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if pkg.URI != baseURL+next {
|
||||
t.Errorf("uri = %q, want %q", pkg.URI, baseURL+next)
|
||||
}
|
||||
for _, r := range pkg.Releases {
|
||||
ids = append(ids, r.ID)
|
||||
}
|
||||
next = ""
|
||||
if pkg.Links != nil && pkg.Links.Next != "" {
|
||||
if !strings.HasPrefix(pkg.Links.Next, baseURL+BasePath+"?") {
|
||||
t.Fatalf("next = %q", pkg.Links.Next)
|
||||
}
|
||||
if !strings.Contains(pkg.Links.Next, "limit=2") ||
|
||||
!strings.Contains(pkg.Links.Next, "updatedFrom=") {
|
||||
t.Errorf("next lost query parameters: %q", pkg.Links.Next)
|
||||
}
|
||||
next = strings.TrimPrefix(pkg.Links.Next, baseURL)
|
||||
}
|
||||
}
|
||||
want := "000002-2026,000003-2026,000004-2026,000005-2026"
|
||||
if got := strings.Join(ids, ","); got != want {
|
||||
t.Errorf("walked %s, want %s", got, want)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user