package service import ( "context" "errors" "testing" "time" easarelease "wucher/internal/domain/easa_release" ) type stubEASARepo struct { row *easarelease.EASARelease updated *easarelease.EASARelease created *easarelease.EASARelease voidedID []byte createErr error getErr error voidErr error } func (s *stubEASARepo) Create(_ context.Context, row *easarelease.EASARelease) error { s.created = row if len(row.ID) == 0 { row.ID = []byte("newid0123456789a") } return s.createErr } func (s *stubEASARepo) Update(_ context.Context, row *easarelease.EASARelease) error { s.updated = row return nil } func (s *stubEASARepo) Delete(_ context.Context, _, _ []byte) error { return nil } func (s *stubEASARepo) GetByID(_ context.Context, _ []byte) (*easarelease.EASARelease, error) { return s.row, s.getErr } func (s *stubEASARepo) GetByFlightID(_ context.Context, _ []byte) (*easarelease.EASARelease, error) { return s.row, s.getErr } func (s *stubEASARepo) GetByComplaintID(_ context.Context, _ []byte) (*easarelease.EASARelease, error) { return s.row, s.getErr } func (s *stubEASARepo) ListByHelicopter(_ context.Context, _ []byte) ([]easarelease.EASARelease, error) { if s.row != nil { return []easarelease.EASARelease{*s.row}, nil } return nil, nil } func (s *stubEASARepo) GetLatestByHelicopter(_ context.Context, _ []byte) (*easarelease.EASARelease, error) { return s.row, nil } func (s *stubEASARepo) GetActiveByHelicopter(_ context.Context, _ []byte) (*easarelease.EASARelease, error) { return s.row, nil } func (s *stubEASARepo) VoidByID(_ context.Context, id []byte, _ string, _ []byte) error { s.voidedID = id return s.voidErr } func completeUnsigned() *easarelease.EASARelease { now := time.Now() return &easarelease.EASARelease{ ID: []byte("0123456789abcdef"), HelicopterID: []byte("fedcba9876543210"), EASADate: &now, EASAACHours: ptr("1245.3"), EASALocation: ptr("LOWI"), EASAWONo: ptr("WO-1"), EASASignerID: ptr("019d61db-53c6-7280-87ec-e65f205c6d3b"), } } func TestEASAReleaseServiceSign(t *testing.T) { actor := []byte("aaaaaaaaaaaaaaaa") ctx := context.Background() if err := NewEASAReleaseService(&stubEASARepo{row: nil}).Sign(ctx, []byte("x"), actor); !errors.Is(err, easarelease.ErrNotFound) { t.Fatalf("want ErrNotFound, got %v", err) } incompleteRepo := &stubEASARepo{row: &easarelease.EASARelease{ID: []byte("0123456789abcdef")}} if err := NewEASAReleaseService(incompleteRepo).Sign(ctx, incompleteRepo.row.ID, actor); !errors.Is(err, easarelease.ErrIncompleteForSign) { t.Fatalf("want ErrIncompleteForSign, got %v", err) } if incompleteRepo.updated != nil { t.Fatal("incomplete release must not be persisted signed") } signed := completeUnsigned() at := time.Now() signed.SignedAt = &at if err := NewEASAReleaseService(&stubEASARepo{row: signed}).Sign(ctx, signed.ID, actor); !errors.Is(err, easarelease.ErrAlreadySigned) { t.Fatalf("want ErrAlreadySigned, got %v", err) } okRepo := &stubEASARepo{row: completeUnsigned()} if err := NewEASAReleaseService(okRepo).Sign(ctx, okRepo.row.ID, actor); err != nil { t.Fatalf("unexpected err: %v", err) } if okRepo.updated == nil || okRepo.updated.SignedAt == nil || string(okRepo.updated.SignedBy) != string(actor) { t.Fatal("complete release should be persisted signed by actor") } } func TestEASAReleaseServiceAmendAndDelegation(t *testing.T) { ctx := context.Background() repo := &stubEASARepo{row: completeUnsigned()} svc := NewEASAReleaseService(repo) newID, err := svc.Amend(ctx, repo.row.ID, "superseded", []byte("actor")) if err != nil { t.Fatalf("Amend err: %v", err) } if string(repo.voidedID) != string(repo.row.ID) { t.Fatal("Amend should void the current release") } if repo.created == nil || string(repo.created.HelicopterID) != string(repo.row.HelicopterID) { t.Fatal("Amend should create a copy for the same helicopter") } if len(newID) == 0 { t.Fatal("Amend should return the new id") } if _, err := svc.GetLatestByHelicopter(ctx, []byte("h")); err != nil { t.Fatalf("GetLatestByHelicopter: %v", err) } if _, err := svc.GetActiveByHelicopter(ctx, []byte("h")); err != nil { t.Fatalf("GetActiveByHelicopter: %v", err) } if rows, err := svc.ListByHelicopter(ctx, []byte("h")); err != nil || len(rows) != 1 { t.Fatalf("ListByHelicopter: %v", err) } if err := svc.Delete(ctx, []byte("x"), nil); err != nil { t.Fatalf("Delete: %v", err) } if _, err := svc.GetByID(ctx, []byte("x")); err != nil { t.Fatalf("GetByID: %v", err) } // Amend on missing release. if _, err := NewEASAReleaseService(&stubEASARepo{row: nil}).Amend(ctx, []byte("x"), "r", nil); !errors.Is(err, easarelease.ErrNotFound) { t.Fatalf("Amend missing: want ErrNotFound, got %v", err) } // Amend with void error. if _, err := NewEASAReleaseService(&stubEASARepo{row: completeUnsigned(), voidErr: errors.New("boom")}).Amend(ctx, []byte("x"), "r", nil); err == nil { t.Fatal("Amend should propagate void error") } // Amend with create error. if _, err := NewEASAReleaseService(&stubEASARepo{row: completeUnsigned(), createErr: errors.New("boom")}).Amend(ctx, []byte("x"), "r", nil); err == nil { t.Fatal("Amend should propagate create error") } // Amend with GetByID error. if _, err := NewEASAReleaseService(&stubEASARepo{getErr: errors.New("boom")}).Amend(ctx, []byte("x"), "r", nil); err == nil { t.Fatal("Amend should propagate GetByID error") } } func TestEASAReleaseServiceCreateUpdateAndErrors(t *testing.T) { ctx := context.Background() repo := &stubEASARepo{} svc := NewEASAReleaseService(repo) row := completeUnsigned() if err := svc.Create(ctx, row); err != nil || repo.created != row { t.Fatalf("Create not delegated: %v", err) } if err := svc.Update(ctx, row); err != nil || repo.updated != row { t.Fatalf("Update not delegated: %v", err) } // Sign with GetByID error. if err := NewEASAReleaseService(&stubEASARepo{getErr: errors.New("boom")}).Sign(ctx, []byte("x"), nil); err == nil { t.Fatal("Sign should propagate GetByID error") } }