Added unit tests, documentation and function descriptions across the board. Increased cyclop complexity threshold to 15 which is a bit more sane than 10 and excluded _test.go files from linters.
This commit is contained in:
1 parent
005b335371
commit
7fd321df97
31 files changed
+4106
-1
No files matched your search
@@ -0,0 +1,215 @@
|
||||
package mock
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"log"
|
||||
"math/rand"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.avsdev.uk/Go/job-search/internal/mock/findATender/ocds"
|
||||
)
|
||||
|
||||
func quietLog(t *testing.T) {
|
||||
t.Helper()
|
||||
w := log.Writer()
|
||||
log.SetOutput(io.Discard)
|
||||
t.Cleanup(func() { log.SetOutput(w) })
|
||||
}
|
||||
|
||||
func TestGap(t *testing.T) {
|
||||
//nolint:gosec // G404: seeded math/rand keeps the test deterministic
|
||||
g := &Generator{rnd: rand.New(rand.NewSource(1))}
|
||||
if d := g.gap(time.Second, time.Second); d != time.Second {
|
||||
t.Errorf("min == max: %s", d)
|
||||
}
|
||||
if d := g.gap(2*time.Second, time.Second); d != 2*time.Second {
|
||||
t.Errorf("max < min: %s, want min", d)
|
||||
}
|
||||
for i := 0; i < 1000; i++ {
|
||||
if d := g.gap(time.Second, 2*time.Second); d < time.Second || d > 2*time.Second {
|
||||
t.Fatalf("gap out of range: %s", d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestScaleValue(t *testing.T) {
|
||||
scaleValue(nil, 2) // must not panic
|
||||
|
||||
v := &ocds.Value{Amount: ptr(12345.0), Currency: "GBP"}
|
||||
scaleValue(v, 1.5)
|
||||
if *v.Amount != 18500 { // 18517.5 rounded to the nearest 100
|
||||
t.Errorf("amount = %v, want 18500", *v.Amount)
|
||||
}
|
||||
if v.AmountGross != nil {
|
||||
t.Error("nil gross amount was set")
|
||||
}
|
||||
|
||||
v = &ocds.Value{Amount: ptr(1000.0), AmountGross: ptr(1200.0)}
|
||||
scaleValue(v, 0.5)
|
||||
if *v.Amount != 500 || *v.AmountGross != 600 {
|
||||
t.Errorf("amounts = %v / %v, want 500 / 600", *v.Amount, *v.AmountGross)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsurePartyID(t *testing.T) {
|
||||
p := ocds.Organization{ID: "GB-EXISTING", Name: "Council"}
|
||||
ensurePartyID(&p)
|
||||
if p.ID != "GB-EXISTING" {
|
||||
t.Errorf("existing id replaced: %s", p.ID)
|
||||
}
|
||||
|
||||
a := ocds.Organization{Name: "Somewhere Council"}
|
||||
b := ocds.Organization{Name: "Somewhere Council"}
|
||||
c := ocds.Organization{Name: "Elsewhere Council"}
|
||||
ensurePartyID(&a)
|
||||
ensurePartyID(&b)
|
||||
ensurePartyID(&c)
|
||||
if !strings.HasPrefix(a.ID, "GB-FTS-") {
|
||||
t.Errorf("id = %q", a.ID)
|
||||
}
|
||||
if a.ID != b.ID {
|
||||
t.Errorf("same name gave %s and %s", a.ID, b.ID)
|
||||
}
|
||||
if a.ID == c.ID {
|
||||
t.Errorf("different names gave the same id %s", a.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdvance(t *testing.T) {
|
||||
cat := loadTestCatalog(t)
|
||||
store, err := OpenStore(filepath.Join(t.TempDir(), "r.jsonl"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer store.Close()
|
||||
g := NewGenerator(cat, store, 1)
|
||||
at := time.Date(2026, 10, 8, 12, 0, 0, 0, time.UTC)
|
||||
|
||||
// A planning notice without a tender becomes an active tender.
|
||||
r := g.advance(ocds.Release{OCID: "ocds-x", Tag: []string{"planning"}}, at)
|
||||
if r.Tag[0] != "tender" || r.Tender == nil || r.Tender.Status != "active" ||
|
||||
r.Tender.TenderPeriod == nil || r.Tender.TenderPeriod.EndDate == nil {
|
||||
t.Fatalf("planning -> %v, tender %+v", r.Tag, r.Tender)
|
||||
}
|
||||
if string(r.Tender.ID) != "ocds-x" || !r.Date.Equal(at) {
|
||||
t.Errorf("tender id %s, date %s", r.Tender.ID, r.Date)
|
||||
}
|
||||
|
||||
// A tender is either updated (deadline + 14 days) or awarded.
|
||||
end := at.AddDate(0, 0, 20)
|
||||
prev := ocds.Release{
|
||||
OCID: "ocds-y",
|
||||
Tag: []string{"tender"},
|
||||
Tender: &ocds.Tender{
|
||||
ID: "ocds-y",
|
||||
Value: &ocds.Value{Amount: ptr(10000.0), Currency: "GBP"},
|
||||
Lots: []ocds.Lot{{ID: "1"}, {ID: ""}},
|
||||
TenderPeriod: &ocds.Period{EndDate: &end},
|
||||
},
|
||||
}
|
||||
seen := map[string]bool{}
|
||||
for i := 0; i < 50; i++ {
|
||||
r := g.advance(prev, at)
|
||||
seen[r.Tag[0]] = true
|
||||
switch r.Tag[0] {
|
||||
case "tenderUpdate":
|
||||
if got := *r.Tender.TenderPeriod.EndDate; !got.Equal(end.AddDate(0, 0, 14)) {
|
||||
t.Fatalf("updated deadline %s", got)
|
||||
}
|
||||
case "award":
|
||||
if r.Tender.Status != "complete" || len(r.Awards) != 1 {
|
||||
t.Fatalf("award: status %s, %d awards", r.Tender.Status, len(r.Awards))
|
||||
}
|
||||
aw := r.Awards[0]
|
||||
if aw.Value == nil || *aw.Value.Amount < 8000 || *aw.Value.Amount > 10000 {
|
||||
t.Fatalf("award value %+v", aw.Value)
|
||||
}
|
||||
if len(aw.RelatedLots) != 1 || aw.RelatedLots[0] != "1" {
|
||||
t.Fatalf("related lots %v", aw.RelatedLots)
|
||||
}
|
||||
if len(aw.Suppliers) != 1 || len(r.Parties) != 1 || r.Parties[0].Roles[0] != "supplier" {
|
||||
t.Fatalf("suppliers %v, parties %v", aw.Suppliers, r.Parties)
|
||||
}
|
||||
default:
|
||||
t.Fatalf("tender -> %s", r.Tag[0])
|
||||
}
|
||||
}
|
||||
if !seen["tenderUpdate"] || !seen["award"] {
|
||||
t.Errorf("expected both updates and awards, saw %v", seen)
|
||||
}
|
||||
if *prev.Tender.TenderPeriod.EndDate != end || prev.Awards != nil {
|
||||
t.Error("advance modified the previous release")
|
||||
}
|
||||
|
||||
// An update without a deadline gets 30 + 14 days; a tender with no value
|
||||
// is awarded with no value.
|
||||
prev = ocds.Release{OCID: "ocds-z", Tag: []string{"tender"}, Tender: &ocds.Tender{ID: "ocds-z"}}
|
||||
for i := 0; i < 50; i++ {
|
||||
r := g.advance(prev, at)
|
||||
switch r.Tag[0] {
|
||||
case "tenderUpdate":
|
||||
if got := *r.Tender.TenderPeriod.EndDate; !got.Equal(at.AddDate(0, 0, 44)) {
|
||||
t.Fatalf("default deadline %s", got)
|
||||
}
|
||||
case "award":
|
||||
if r.Awards[0].Value != nil {
|
||||
t.Fatalf("award value %+v, want nil", r.Awards[0].Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunPublishesUntilCancelled(t *testing.T) {
|
||||
quietLog(t)
|
||||
cat := loadTestCatalog(t)
|
||||
store, err := OpenStore(filepath.Join(t.TempDir(), "r.jsonl"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer store.Close()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
NewGenerator(cat, store, 1).Run(ctx, time.Millisecond, 2*time.Millisecond)
|
||||
close(done)
|
||||
}()
|
||||
|
||||
deadline := time.After(5 * time.Second)
|
||||
for store.Len() < 3 {
|
||||
select {
|
||||
case <-deadline:
|
||||
cancel()
|
||||
t.Fatalf("only %d releases published", store.Len())
|
||||
case <-time.After(time.Millisecond):
|
||||
}
|
||||
}
|
||||
cancel()
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("Run did not return after cancel")
|
||||
}
|
||||
}
|
||||
|
||||
// A failing store is logged and Run carries on until cancelled.
|
||||
func TestRunSurvivesStoreErrors(t *testing.T) {
|
||||
quietLog(t)
|
||||
cat := loadTestCatalog(t)
|
||||
store, err := OpenStore(filepath.Join(t.TempDir(), "r.jsonl"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
store.Close()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond)
|
||||
defer cancel()
|
||||
NewGenerator(cat, store, 1).Run(ctx, time.Millisecond, time.Millisecond)
|
||||
if store.Len() != 0 {
|
||||
t.Errorf("Len = %d, want 0", store.Len())
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user