Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,12 @@ make coverage-check # enforce >= 80% coverage under ./internal
make lint # go vet + golangci-lint
```

The BDD suite lives in `test/bdd` and uses [Ginkgo](https://onsi.github.io/ginkgo/)
and [Gomega](https://onsi.github.io/gomega/) to exercise the public volume
lifecycle: create/export, active-session refusal, credential rotation, and
delete. GoMock is not required yet because the core interfaces are covered by
focused in-package test doubles.
All unit test packages now run through [Ginkgo](https://onsi.github.io/ginkgo/)
suites; the existing case functions execute through Ginkgo's `GinkgoTB` adapter.
The BDD-style suite in `test/bdd` uses [Gomega](https://onsi.github.io/gomega/)
assertions to exercise the public volume lifecycle: create/export,
active-session refusal, credential rotation, and delete. GoMock is not required
yet because the core interfaces are covered by focused in-package test doubles.

`make tools` installs the optional protobuf, CRD, and lint tools. Add
`$(go env GOPATH)/bin` to `PATH` before running `make lint` or `make proto`.
Expand Down
20 changes: 10 additions & 10 deletions internal/agent/admin_drift_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
"google.golang.org/grpc/status"
)

func addDriftExport(t *testing.T, store *state.Store, owner string) {
func addDriftExport(t testing.TB, store *state.Store, owner string) {
t.Helper()
now := time.Date(2026, 11, 6, 12, 0, 0, 0, time.UTC)
if err := store.Transact(context.Background(), func(tx state.Tx) error {
Expand All @@ -29,9 +29,9 @@ func addDriftExport(t *testing.T, store *state.Store, owner string) {
}
}

func TestResolveDriftMarkActions(t *testing.T) {
func bddTestResolveDriftMarkActions(t testing.TB) {
owner := "spiffe://kouja/caller/one"
newService := func(t *testing.T, present bool, targetPresent bool) (*AdminService, *state.Store) {
newService := func(t testing.TB, present bool, targetPresent bool) (*AdminService, *state.Store) {
t.Helper()
store := newAgentStore(t)
addAgentVolume(t, store, owner, "vol1", "resource-1", "drift")
Expand All @@ -51,7 +51,7 @@ func TestResolveDriftMarkActions(t *testing.T) {
return NewAdminService(store, backend, WithTargetExporters(map[string]target.Exporter{"iscsi": exporter})), store
}

t.Run("mark available", func(t *testing.T) {
bddSubtest(t, "mark available", func(t testing.TB) {
service, store := newService(t, true, false)
response, err := service.ResolveDrift(adminContext(), &koujav1.ResolveDriftRequest{
Owner: owner, VolumeId: "vol1", Action: koujav1.ResolveAction_RESOLVE_ACTION_MARK_AVAILABLE,
Expand All @@ -68,7 +68,7 @@ func TestResolveDriftMarkActions(t *testing.T) {
}
})

t.Run("mark exported", func(t *testing.T) {
bddSubtest(t, "mark exported", func(t testing.TB) {
service, store := newService(t, true, true)
response, err := service.ResolveDrift(adminContext(), &koujav1.ResolveDriftRequest{
Owner: owner, VolumeId: "vol1", Action: koujav1.ResolveAction_RESOLVE_ACTION_MARK_EXPORTED,
Expand All @@ -93,7 +93,7 @@ func TestResolveDriftMarkActions(t *testing.T) {
}
})

t.Run("mark failed", func(t *testing.T) {
bddSubtest(t, "mark failed", func(t testing.TB) {
service, store := newService(t, true, false)
if _, err := service.ResolveDrift(adminContext(), &koujav1.ResolveDriftRequest{
Owner: owner, VolumeId: "vol1", Action: koujav1.ResolveAction_RESOLVE_ACTION_MARK_FAILED,
Expand All @@ -106,7 +106,7 @@ func TestResolveDriftMarkActions(t *testing.T) {
}
})

t.Run("mark deleted", func(t *testing.T) {
bddSubtest(t, "mark deleted", func(t testing.TB) {
service, store := newService(t, false, false)
if _, err := service.ResolveDrift(adminContext(), &koujav1.ResolveDriftRequest{
Owner: owner, VolumeId: "vol1", Action: koujav1.ResolveAction_RESOLVE_ACTION_MARK_DELETED,
Expand All @@ -124,7 +124,7 @@ func TestResolveDriftMarkActions(t *testing.T) {
}
})

t.Run("mark available rejects present target", func(t *testing.T) {
bddSubtest(t, "mark available rejects present target", func(t testing.TB) {
service, _ := newService(t, true, true)
if _, err := service.ResolveDrift(adminContext(), &koujav1.ResolveDriftRequest{
Owner: owner, VolumeId: "vol1", Action: koujav1.ResolveAction_RESOLVE_ACTION_MARK_AVAILABLE,
Expand All @@ -133,7 +133,7 @@ func TestResolveDriftMarkActions(t *testing.T) {
}
})

t.Run("non admin rejected", func(t *testing.T) {
bddSubtest(t, "non admin rejected", func(t testing.TB) {
service, _ := newService(t, true, false)
if _, err := service.ResolveDrift(callerContext(owner), &koujav1.ResolveDriftRequest{
Owner: owner, VolumeId: "vol1", Action: koujav1.ResolveAction_RESOLVE_ACTION_MARK_FAILED,
Expand All @@ -143,7 +143,7 @@ func TestResolveDriftMarkActions(t *testing.T) {
})
}

func TestResolveDriftRecreateExport(t *testing.T) {
func bddTestResolveDriftRecreateExport(t testing.TB) {
owner := "spiffe://kouja/caller/one"
store := newAgentStore(t)
addAgentVolume(t, store, owner, "vol1", "resource-1", "drift")
Expand Down
8 changes: 4 additions & 4 deletions internal/agent/admin_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func adminContext() context.Context {
})
}

func TestAdminRegisterAndDeregisterVG(t *testing.T) {
func bddTestAdminRegisterAndDeregisterVG(t testing.TB) {
store := newAgentStore(t)
backend := &fakeLVM{capacity: lvm.Capacity{
VGName: "vg1", VGUUID: "vg-uuid", VGState: "active", TotalBytes: 10,
Expand Down Expand Up @@ -50,7 +50,7 @@ func TestAdminRegisterAndDeregisterVG(t *testing.T) {
}
}

func TestAdminGrantsAndRevokesNodeCredential(t *testing.T) {
func bddTestAdminGrantsAndRevokesNodeCredential(t testing.TB) {
store := newAgentStore(t)
owner := "spiffe://kouja/controller/one"
addAgentVolume(t, store, owner, "vol1", "resource-1", "exported")
Expand Down Expand Up @@ -87,7 +87,7 @@ func TestAdminGrantsAndRevokesNodeCredential(t *testing.T) {
}
}

func TestAdminRejectsNonAdminAndDeregisterWithVolume(t *testing.T) {
func bddTestAdminRejectsNonAdminAndDeregisterWithVolume(t testing.TB) {
store := newAgentStore(t)
owner := "spiffe://kouja/caller/one"
addAgentVolume(t, store, owner, "vol1", "resource-1", "available")
Expand All @@ -101,7 +101,7 @@ func TestAdminRejectsNonAdminAndDeregisterWithVolume(t *testing.T) {
}
}

func TestAdminValidationBranches(t *testing.T) {
func bddTestAdminValidationBranches(t testing.TB) {
store := newAgentStore(t)
owner := "spiffe://kouja/controller/one"
addAgentVolume(t, store, owner, "vol1", "resource-1", "available")
Expand Down
4 changes: 2 additions & 2 deletions internal/agent/admission_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"google.golang.org/grpc/status"
)

func TestAdmissionConcurrencyAndPerCaller(t *testing.T) {
func bddTestAdmissionConcurrencyAndPerCaller(t testing.TB) {
admission := NewAdmission(2, 1, 10)
releaseOne, err := admission.AcquireOperation(context.Background(), "caller-a")
if err != nil {
Expand All @@ -36,7 +36,7 @@ func TestAdmissionConcurrencyAndPerCaller(t *testing.T) {
release()
}

func TestAdmissionCapacityRate(t *testing.T) {
func bddTestAdmissionCapacityRate(t testing.TB) {
admission := NewAdmission(2, 2, 1)
base := time.Date(2026, 11, 6, 12, 0, 0, 0, time.UTC)
admission.now = func() time.Time { return base }
Expand Down
123 changes: 123 additions & 0 deletions internal/agent/ginkgo_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package agent

import (
"testing"

. "github.com/onsi/ginkgo/v2"
gomega "github.com/onsi/gomega"
)

func TestGinkgo(t *testing.T) {
gomega.RegisterFailHandler(Fail)
RunSpecs(t, "agent Suite")
}

func bddSubtest(t testing.TB, name string, fn func(testing.TB)) {
By(name)
fn(t)
}

var _ = Describe("unit tests", func() {
It("TestResolveDriftMarkActions", func() {
bddTestResolveDriftMarkActions(GinkgoTB())
})
It("TestResolveDriftRecreateExport", func() {
bddTestResolveDriftRecreateExport(GinkgoTB())
})
It("TestAdminRegisterAndDeregisterVG", func() {
bddTestAdminRegisterAndDeregisterVG(GinkgoTB())
})
It("TestAdminGrantsAndRevokesNodeCredential", func() {
bddTestAdminGrantsAndRevokesNodeCredential(GinkgoTB())
})
It("TestAdminRejectsNonAdminAndDeregisterWithVolume", func() {
bddTestAdminRejectsNonAdminAndDeregisterWithVolume(GinkgoTB())
})
It("TestAdminValidationBranches", func() {
bddTestAdminValidationBranches(GinkgoTB())
})
It("TestAdmissionConcurrencyAndPerCaller", func() {
bddTestAdmissionConcurrencyAndPerCaller(GinkgoTB())
})
It("TestAdmissionCapacityRate", func() {
bddTestAdmissionCapacityRate(GinkgoTB())
})
It("TestAgentProtoMappings", func() {
bddTestAgentProtoMappings(GinkgoTB())
})
It("TestCheckDependenciesWithFakeTools", func() {
bddTestCheckDependenciesWithFakeTools(GinkgoTB())
})
It("TestAgentAdmissionAndIdentityErrors", func() {
bddTestAgentAdmissionAndIdentityErrors(GinkgoTB())
})
It("TestInfoAndListBranches", func() {
bddTestInfoAndListBranches(GinkgoTB())
})
It("TestCapacityAdmissionAndReadyBranches", func() {
bddTestCapacityAdmissionAndReadyBranches(GinkgoTB())
})
It("TestVolumeServiceIdentityFailures", func() {
bddTestVolumeServiceIdentityFailures(GinkgoTB())
})
It("TestCapacityUnavailableBranch", func() {
bddTestCapacityUnavailableBranch(GinkgoTB())
})
It("TestRequestIDAndNilErrors", func() {
bddTestRequestIDAndNilErrors(GinkgoTB())
})
It("TestReconcileWritesObservationsAndDrift", func() {
bddTestReconcileWritesObservationsAndDrift(GinkgoTB())
})
It("TestServerServesGRPCAndHTTPOnOneTLSListener", func() {
bddTestServerServesGRPCAndHTTPOnOneTLSListener(GinkgoTB())
})
It("TestNewServerLifecycleAndErrors", func() {
bddTestNewServerLifecycleAndErrors(GinkgoTB())
})
It("TestDependencyChecksAndReconcileLoop", func() {
bddTestDependencyChecksAndReconcileLoop(GinkgoTB())
})
It("TestUnaryInterceptorPropagatesIdentityAndRequestID", func() {
bddTestUnaryInterceptorPropagatesIdentityAndRequestID(GinkgoTB())
})
It("TestUnaryInterceptorRejectsMissingOrInvalidCertificate", func() {
bddTestUnaryInterceptorRejectsMissingOrInvalidCertificate(GinkgoTB())
})
It("TestInitializeVGsSeedsOnlyOnce", func() {
bddTestInitializeVGsSeedsOnlyOnce(GinkgoTB())
})
It("TestVerifyVGsMarksUnavailableAndVolumesDrift", func() {
bddTestVerifyVGsMarksUnavailableAndVolumesDrift(GinkgoTB())
})
It("TestVerifyVGsRejectsUUIDMismatch", func() {
bddTestVerifyVGsRejectsUUIDMismatch(GinkgoTB())
})
It("TestCreateVolumeBuildsCanonicalIntent", func() {
bddTestCreateVolumeBuildsCanonicalIntent(GinkgoTB())
})
It("TestCreateVolumeUnexportedDoesNotRequireProtocol", func() {
bddTestCreateVolumeUnexportedDoesNotRequireProtocol(GinkgoTB())
})
It("TestGetVolumeOwnerScopingAndMapping", func() {
bddTestGetVolumeOwnerScopingAndMapping(GinkgoTB())
})
It("TestListVolumesPaginationAndCapacity", func() {
bddTestListVolumesPaginationAndCapacity(GinkgoTB())
})
It("TestOperationErrorMapsToStatus", func() {
bddTestOperationErrorMapsToStatus(GinkgoTB())
})
It("TestGetCredentialRevealsSecret", func() {
bddTestGetCredentialRevealsSecret(GinkgoTB())
})
It("TestVolumeServiceMutationRPCsAndCredentialValue", func() {
bddTestVolumeServiceMutationRPCsAndCredentialValue(GinkgoTB())
})
It("TestVolumeServiceHelpersAndStatusErrors", func() {
bddTestVolumeServiceHelpersAndStatusErrors(GinkgoTB())
})
It("TestVolumeServiceRPCErrorBranches", func() {
bddTestVolumeServiceRPCErrorBranches(GinkgoTB())
})
})
16 changes: 8 additions & 8 deletions internal/agent/misc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
"github.com/voidstarit/kouja/internal/state"
)

func TestAgentProtoMappings(t *testing.T) {
func bddTestAgentProtoMappings(t testing.TB) {
observation := toProtoObservation(state.Observation{
LVPresent: true, ObservedSizeBytes: 10, DevicePath: "/dev/x", ObservedAt: time.Now(), Source: "lvm",
}, state.ExportObservation{
Expand Down Expand Up @@ -62,7 +62,7 @@ func TestAgentProtoMappings(t *testing.T) {
}
}

func TestCheckDependenciesWithFakeTools(t *testing.T) {
func bddTestCheckDependenciesWithFakeTools(t testing.TB) {
dir := t.TempDir()
for _, name := range []string{"lvs", "vgs", "pvs", "lvcreate", "lvremove", "lvchange", "udevadm", "findmnt", "blockdev", "systemctl"} {
if err := os.WriteFile(filepath.Join(dir, name), []byte("#!/bin/sh\nexit 0\n"), 0o755); err != nil { //nolint:gosec // executable test fixture
Expand All @@ -81,7 +81,7 @@ func TestCheckDependenciesWithFakeTools(t *testing.T) {
}
}

func TestAgentAdmissionAndIdentityErrors(t *testing.T) {
func bddTestAgentAdmissionAndIdentityErrors(t testing.TB) {
manager := &fakeCredentialManager{err: errors.New("missing")}
service := NewVolumeService(newAgentStore(t), &fakeLVM{}, &fakeRunner{}, manager, ServiceOptions{})
if _, err := service.GetInfo(context.Background(), &koujav1.GetInfoRequest{}); err != nil {
Expand All @@ -99,7 +99,7 @@ func TestAgentAdmissionAndIdentityErrors(t *testing.T) {
_ = WithDriftRetrier(nil)
}

func TestInfoAndListBranches(t *testing.T) {
func bddTestInfoAndListBranches(t testing.TB) {
store := newAgentStore(t)
ready := false
service := NewVolumeService(store, &fakeLVM{}, &fakeRunner{}, &fakeCredentialManager{}, ServiceOptions{
Expand All @@ -120,7 +120,7 @@ func TestInfoAndListBranches(t *testing.T) {
}
}

func TestCapacityAdmissionAndReadyBranches(t *testing.T) {
func bddTestCapacityAdmissionAndReadyBranches(t testing.TB) {
store := newAgentStore(t)
admission := NewAdmission(1, 1, 1)
service := NewVolumeService(store, &fakeLVM{}, &fakeRunner{}, &fakeCredentialManager{}, ServiceOptions{
Expand All @@ -138,7 +138,7 @@ func TestCapacityAdmissionAndReadyBranches(t *testing.T) {
}
}

func TestVolumeServiceIdentityFailures(t *testing.T) {
func bddTestVolumeServiceIdentityFailures(t testing.TB) {
store := newAgentStore(t)
service := NewVolumeService(store, &fakeLVM{}, &fakeRunner{}, &fakeCredentialManager{}, ServiceOptions{})
if _, err := service.GetCredential(context.Background(), &koujav1.GetCredentialRequest{VolumeId: "vol"}); err == nil {
Expand All @@ -152,7 +152,7 @@ func TestVolumeServiceIdentityFailures(t *testing.T) {
}
}

func TestCapacityUnavailableBranch(t *testing.T) {
func bddTestCapacityUnavailableBranch(t testing.TB) {
store := newAgentStore(t)
addAgentVolume(t, store, "owner", "vol1", "resource-1", "available")
service := NewVolumeService(store, &fakeLVM{capacityErr: errors.New("lvm offline")}, &fakeRunner{}, &fakeCredentialManager{}, ServiceOptions{})
Expand All @@ -162,7 +162,7 @@ func TestCapacityUnavailableBranch(t *testing.T) {
}
}

func TestRequestIDAndNilErrors(t *testing.T) {
func bddTestRequestIDAndNilErrors(t testing.TB) {
if statusError(nil) != nil {
t.Fatal("nil error did not map to nil")
}
Expand Down
2 changes: 1 addition & 1 deletion internal/agent/reconcile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func (exporter *reconcileTarget) Restore(context.Context, target.ExportSpec, tar
}
func (exporter *reconcileTarget) Delete(context.Context, target.ExportSpec, bool) error { return nil }

func TestReconcileWritesObservationsAndDrift(t *testing.T) {
func bddTestReconcileWritesObservationsAndDrift(t testing.TB) {
store := newAgentStore(t)
owner := "spiffe://kouja/caller/one"
addAgentVolume(t, store, owner, "vol1", "resource-1", "exported")
Expand Down
8 changes: 4 additions & 4 deletions internal/agent/server_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import (
"google.golang.org/grpc/credentials"
)

func writeTestCertificates(t *testing.T) (caFile, serverCert, serverKey, clientCert, clientKey string) {
func writeTestCertificates(t testing.TB) (caFile, serverCert, serverKey, clientCert, clientKey string) {
t.Helper()
dir := t.TempDir()
caKey, err := rsa.GenerateKey(rand.Reader, 2048)
Expand Down Expand Up @@ -59,7 +59,7 @@ func writeTestCertificates(t *testing.T) (caFile, serverCert, serverKey, clientC
return caFile, serverCertFile, serverKeyFile, clientCertFile, clientKeyFile
}

func issueCertificate(t *testing.T, dir string, ca *x509.Certificate, caKey *rsa.PrivateKey, identity string, server bool) (keyFile, certFile string) {
func issueCertificate(t testing.TB, dir string, ca *x509.Certificate, caKey *rsa.PrivateKey, identity string, server bool) (keyFile, certFile string) {
t.Helper()
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
Expand Down Expand Up @@ -99,7 +99,7 @@ func issueCertificate(t *testing.T, dir string, ca *x509.Certificate, caKey *rsa
return keyFile, certFile
}

func writePEM(t *testing.T, path, blockType string, data []byte) {
func writePEM(t testing.TB, path, blockType string, data []byte) {
t.Helper()
file, err := os.Create(path) //nolint:gosec // temporary test certificate path
if err != nil {
Expand All @@ -111,7 +111,7 @@ func writePEM(t *testing.T, path, blockType string, data []byte) {
}
}

func TestServerServesGRPCAndHTTPOnOneTLSListener(t *testing.T) {
func bddTestServerServesGRPCAndHTTPOnOneTLSListener(t testing.TB) {
caFile, serverCert, serverKey, clientCert, clientKey := writeTestCertificates(t)
cfg := &config.Config{}
cfg.Listen.Address = "127.0.0.1:0"
Expand Down
Loading
Loading