From 569b977f6574db4390c49eb5297faa0451a020d8 Mon Sep 17 00:00:00 2001 From: ykaiboussiSO Date: Wed, 21 Oct 2026 20:25:48 -0500 Subject: [PATCH 1/4] chore(BDD): Refactor DB connection at scenario level Resolves: BED-9992 --- bdd/steps_test.go | 43 +++++---------------- bdd/utils.go | 26 +++++++++++++ bdd/utils_test.go | 98 ++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 124 insertions(+), 43 deletions(-) diff --git a/bdd/steps_test.go b/bdd/steps_test.go index 904cc82a..445fbf17 100644 --- a/bdd/steps_test.go +++ b/bdd/steps_test.go @@ -20,14 +20,10 @@ package bdd import ( - "context" - "log" "os" "testing" "github.com/cucumber/godog" - "github.com/specterops/dawgs/graph" - "github.com/specterops/dawgs/integration" ) var ( @@ -35,18 +31,6 @@ var ( bddReportInvalid bool ) -func InitializeTestSuite(ctxtestsuite *godog.TestSuiteContext, ctx context.Context, c *dbContext) { - err := c.db.WriteTransaction(ctx, func(tx graph.Transaction) error { - if err := tx.Nodes().Delete(); err != nil { - return err - } - return nil - }) - if err != nil { - log.Fatalf("Failed to clear database %v", err) - } -} - func InitializeScenario(ctx *godog.ScenarioContext, dbCtx *dbContext) { ctx.Before(dbCtx.resetBeforeScenario) ctx.Step(`^an empty graph$`, dbCtx.anEmptyGraph) @@ -58,38 +42,29 @@ func InitializeScenario(ctx *godog.ScenarioContext, dbCtx *dbContext) { ctx.Step(`^no side effects$`, dbCtx.noSideEffects) ctx.Step(`^the side effects should be:$`, dbCtx.theSideEffectsShouldBe) ctx.Step(`^the result should be empty$`, dbCtx.theResultShouldBeEmpty) + ctx.After(dbCtx.closeDBSession) } func TestFeatures(t *testing.T) { - backgroundCtx := context.Background() if err := os.MkdirAll(bddReportDir, 0o755); err != nil { t.Fatalf("failed to create BDD report directory: %v", err) } - // establish database connection - session := integration.Open(t, integration.Options{ - Schema: &graph.Schema{ - DefaultGraph: graph.Graph{ - Name: "dawgs-bdd", - }, - }, - }) - dbCtx := &dbContext{ - db: session.DB, - testData: []string{"testdata/binary-tree-1.json", "testdata/binary-tree-2.json"}, - } + dbCtx := &dbContext{} + suite := godog.TestSuite{ Name: "OpenCypher-TCK", - TestSuiteInitializer: func(ctx *godog.TestSuiteContext) { - InitializeTestSuite(ctx, backgroundCtx, dbCtx) + TestSuiteInitializer: func(_ *godog.TestSuiteContext) { + dbCtx.testData = []string{"testdata/binary-tree-1.json", "testdata/binary-tree-2.json"} }, ScenarioInitializer: func(ctx *godog.ScenarioContext) { InitializeScenario(ctx, dbCtx) }, Options: &godog.Options{ - Format: "pretty,cucumber:" + bddJSONReportPath, - Paths: []string{"features"}, - TestingT: t, + Format: "pretty,cucumber:" + bddJSONReportPath, + Paths: []string{"features"}, + TestingT: t, + Concurrency: 1, }, } diff --git a/bdd/utils.go b/bdd/utils.go index b7c597ef..8b011474 100644 --- a/bdd/utils.go +++ b/bdd/utils.go @@ -30,6 +30,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/specterops/dawgs/cypher/frontend" "github.com/specterops/dawgs/graph" + "github.com/specterops/dawgs/integration" "github.com/specterops/dawgs/opengraph" ) @@ -40,6 +41,7 @@ type graphSnapshot struct { type dbContext struct { db graph.Database + openSession func(integration.Options) (*integration.Session, error) testData []string beforeExecution graphSnapshot afterExecution graphSnapshot @@ -50,6 +52,23 @@ type dbContext struct { // resetBeforeScenario clears persistent graph data and scenario-local result state. func (c *dbContext) resetBeforeScenario(ctx context.Context, _ *godog.Scenario) (context.Context, error) { + openSession := c.openSession + if openSession == nil { + openSession = integration.OpenSession + } + session, err := openSession(integration.Options{ + Schema: &graph.Schema{ + DefaultGraph: graph.Graph{ + Name: "dawgs-bdd", + }, + }, + }) + if err != nil { + return ctx, fmt.Errorf("failed to establish database connection error: %w", err) + } + + c.db = session.DB + if err := c.anEmptyGraph(ctx); err != nil { return ctx, fmt.Errorf("reset graph before scenario: %w", err) } @@ -62,6 +81,13 @@ func (c *dbContext) resetBeforeScenario(ctx context.Context, _ *godog.Scenario) return ctx, nil } +func (c *dbContext) closeDBSession(ctx context.Context, _ *godog.Scenario, _ error) (context.Context, error) { + if err := c.db.Close(context.Background()); err != nil { + return ctx, err + } + return ctx, nil +} + // anEmptyGraph removes all nodes and relationships from the graph. func (c *dbContext) anEmptyGraph(ctx context.Context) error { err := c.db.WriteTransaction(ctx, func(tx graph.Transaction) error { diff --git a/bdd/utils_test.go b/bdd/utils_test.go index 437a10cf..c409c077 100644 --- a/bdd/utils_test.go +++ b/bdd/utils_test.go @@ -23,6 +23,7 @@ import ( "github.com/cucumber/godog" messages "github.com/cucumber/messages/go/v34" "github.com/specterops/dawgs/graph" + "github.com/specterops/dawgs/integration" "github.com/stretchr/testify/require" ) @@ -214,28 +215,37 @@ func TestDBContextAnEmptyGraph(t *testing.T) { func TestDBContextResetBeforeScenario(t *testing.T) { testContext := context.WithValue(context.Background(), struct{}{}, "scenario") + oldDatabase := &stubDatabase{} nodeQuery := &stubNodeQuery{} database := &stubDatabase{ transaction: &stubTransaction{nodeQuery: nodeQuery}, } databaseContext := &dbContext{ - db: database, + db: oldDatabase, + openSession: func(opts integration.Options) (*integration.Session, error) { + require.Equal(t, &graph.Schema{DefaultGraph: graph.Graph{Name: "dawgs-bdd"}}, opts.Schema) + return &integration.Session{DB: database}, nil + }, beforeExecution: graphSnapshot{ NodesCount: 2, RelationshipsCount: 1, }, actualResult: &stubResult{}, rowCount: 2, - actualRows: [][]string{{"stale"}}, + actualRows: [][]string{{}}, + testData: []string{"testdata/binary-tree-1.json", "testdata/binary-tree-2.json"}, } returnedContext, err := databaseContext.resetBeforeScenario(testContext, nil) require.NoError(t, err) require.Equal(t, testContext, returnedContext) + require.Same(t, database, databaseContext.db) + require.False(t, oldDatabase.writeTransactionCalled) require.True(t, database.writeTransactionCalled) require.Equal(t, testContext, database.writeTransactionContext) require.True(t, nodeQuery.deleteCalled) + require.Equal(t, []string{"testdata/binary-tree-1.json", "testdata/binary-tree-2.json"}, databaseContext.testData) require.Equal(t, graphSnapshot{}, databaseContext.beforeExecution) require.Nil(t, databaseContext.actualResult) require.Zero(t, databaseContext.rowCount) @@ -244,17 +254,87 @@ func TestDBContextResetBeforeScenario(t *testing.T) { func TestDBContextResetBeforeScenarioReturnsCleanupError(t *testing.T) { deleteErr := errors.New("failed to delete nodes") - database := &stubDatabase{ - transaction: &stubTransaction{ - nodeQuery: &stubNodeQuery{deleteErr: deleteErr}, + writeErr := errors.New("failed to open write transaction") + tests := []struct { + name string + deleteErr error + writeErr error + expectedErr error + expectDelete bool + }{ + {name: "node deletion error", deleteErr: deleteErr, expectedErr: deleteErr, expectDelete: true}, + {name: "write transaction error", writeErr: writeErr, expectedErr: writeErr}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + testContext := context.WithValue(context.Background(), struct{}{}, test.name) + oldDatabase := &stubDatabase{} + nodeQuery := &stubNodeQuery{deleteErr: test.deleteErr} + database := &stubDatabase{ + transaction: &stubTransaction{nodeQuery: nodeQuery}, + writeTransactionErr: test.writeErr, + } + result := &stubResult{} + snapshot := graphSnapshot{NodesCount: 2, RelationshipsCount: 1} + databaseContext := &dbContext{ + db: oldDatabase, + openSession: func(integration.Options) (*integration.Session, error) { + return &integration.Session{DB: database}, nil + }, + beforeExecution: snapshot, + actualResult: result, + rowCount: 2, + actualRows: [][]string{{"stale"}}, + } + + returnedContext, err := databaseContext.resetBeforeScenario(testContext, nil) + + require.ErrorIs(t, err, test.expectedErr) + require.ErrorContains(t, err, "reset graph before scenario") + require.Equal(t, testContext, returnedContext) + require.Same(t, database, databaseContext.db) + require.False(t, oldDatabase.writeTransactionCalled) + require.True(t, database.writeTransactionCalled) + require.Equal(t, testContext, database.writeTransactionContext) + require.Equal(t, test.expectDelete, nodeQuery.deleteCalled) + require.Equal(t, snapshot, databaseContext.beforeExecution) + require.Same(t, result, databaseContext.actualResult) + require.Equal(t, 2, databaseContext.rowCount) + require.Equal(t, [][]string{{"stale"}}, databaseContext.actualRows) + }) + } +} + +func TestDBContextResetBeforeScenarioReturnsSessionError(t *testing.T) { + testContext := context.WithValue(context.Background(), struct{}{}, "scenario") + openErr := errors.New("failed to open session") + database := &stubDatabase{} + result := &stubResult{} + snapshot := graphSnapshot{NodesCount: 2, RelationshipsCount: 1} + databaseContext := &dbContext{ + db: database, + openSession: func(integration.Options) (*integration.Session, error) { + return nil, openErr }, + testData: []string{"existing fixture"}, + beforeExecution: snapshot, + actualResult: result, + rowCount: 2, + actualRows: [][]string{{"stale"}}, } - databaseContext := &dbContext{db: database} - _, err := databaseContext.resetBeforeScenario(context.Background(), nil) + returnedContext, err := databaseContext.resetBeforeScenario(testContext, nil) - require.ErrorIs(t, err, deleteErr) - require.ErrorContains(t, err, "reset graph before scenario") + require.ErrorIs(t, err, openErr) + require.ErrorContains(t, err, "failed to establish database connection error") + require.Equal(t, testContext, returnedContext) + require.Same(t, database, databaseContext.db) + require.False(t, database.writeTransactionCalled) + require.Equal(t, []string{"existing fixture"}, databaseContext.testData) + require.Equal(t, snapshot, databaseContext.beforeExecution) + require.Same(t, result, databaseContext.actualResult) + require.Equal(t, 2, databaseContext.rowCount) + require.Equal(t, [][]string{{"stale"}}, databaseContext.actualRows) } func TestDBContextExecutingQuery(t *testing.T) { From 9d1d585a65b8b83d0f019d7be335f0a5d795de62 Mon Sep 17 00:00:00 2001 From: ykaiboussiSO Date: Thu, 29 Oct 2026 09:43:52 -0500 Subject: [PATCH 2/4] address CR feedback --- bdd/utils.go | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/bdd/utils.go b/bdd/utils.go index 8b011474..6ea8f558 100644 --- a/bdd/utils.go +++ b/bdd/utils.go @@ -52,6 +52,12 @@ type dbContext struct { // resetBeforeScenario clears persistent graph data and scenario-local result state. func (c *dbContext) resetBeforeScenario(ctx context.Context, _ *godog.Scenario) (context.Context, error) { + c.db = nil + c.beforeExecution = graphSnapshot{} + c.actualResult = nil + c.rowCount = 0 + c.actualRows = nil + openSession := c.openSession if openSession == nil { openSession = integration.OpenSession @@ -72,17 +78,17 @@ func (c *dbContext) resetBeforeScenario(ctx context.Context, _ *godog.Scenario) if err := c.anEmptyGraph(ctx); err != nil { return ctx, fmt.Errorf("reset graph before scenario: %w", err) } - - c.beforeExecution = graphSnapshot{} - c.actualResult = nil - c.rowCount = 0 - c.actualRows = nil - return ctx, nil } func (c *dbContext) closeDBSession(ctx context.Context, _ *godog.Scenario, _ error) (context.Context, error) { - if err := c.db.Close(context.Background()); err != nil { + if c.db == nil { + return ctx, nil + } + + db := c.db + c.db = nil + if err := db.Close(context.Background()); err != nil { return ctx, err } return ctx, nil From 0ea3610fc4d563e9cab87dd5400f4c1637098cfc Mon Sep 17 00:00:00 2001 From: ykaiboussiSO Date: Thu, 29 Oct 2026 10:17:58 -0500 Subject: [PATCH 3/4] fix CI tests --- bdd/utils_test.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/bdd/utils_test.go b/bdd/utils_test.go index c409c077..f4563cde 100644 --- a/bdd/utils_test.go +++ b/bdd/utils_test.go @@ -297,10 +297,10 @@ func TestDBContextResetBeforeScenarioReturnsCleanupError(t *testing.T) { require.True(t, database.writeTransactionCalled) require.Equal(t, testContext, database.writeTransactionContext) require.Equal(t, test.expectDelete, nodeQuery.deleteCalled) - require.Equal(t, snapshot, databaseContext.beforeExecution) - require.Same(t, result, databaseContext.actualResult) - require.Equal(t, 2, databaseContext.rowCount) - require.Equal(t, [][]string{{"stale"}}, databaseContext.actualRows) + require.Equal(t, graphSnapshot{}, databaseContext.beforeExecution) + require.Nil(t, databaseContext.actualResult) + require.Zero(t, databaseContext.rowCount) + require.Nil(t, databaseContext.actualRows) }) } } @@ -328,13 +328,13 @@ func TestDBContextResetBeforeScenarioReturnsSessionError(t *testing.T) { require.ErrorIs(t, err, openErr) require.ErrorContains(t, err, "failed to establish database connection error") require.Equal(t, testContext, returnedContext) - require.Same(t, database, databaseContext.db) + require.Nil(t, databaseContext.db) require.False(t, database.writeTransactionCalled) require.Equal(t, []string{"existing fixture"}, databaseContext.testData) - require.Equal(t, snapshot, databaseContext.beforeExecution) - require.Same(t, result, databaseContext.actualResult) - require.Equal(t, 2, databaseContext.rowCount) - require.Equal(t, [][]string{{"stale"}}, databaseContext.actualRows) + require.Equal(t, graphSnapshot{NodesCount: 0, RelationshipsCount: 0}, databaseContext.beforeExecution) + require.Nil(t, databaseContext.actualResult) + require.Equal(t, 0, databaseContext.rowCount) + require.Nil(t, databaseContext.actualRows) } func TestDBContextExecutingQuery(t *testing.T) { From d70207a5890674c59301235b3921f71ad92ae0ba Mon Sep 17 00:00:00 2001 From: ykaiboussiSO Date: Fri, 30 Oct 2026 10:19:50 -0500 Subject: [PATCH 4/4] reset db before closing db session --- bdd/utils.go | 1 + 1 file changed, 1 insertion(+) diff --git a/bdd/utils.go b/bdd/utils.go index 6ea8f558..c809f55f 100644 --- a/bdd/utils.go +++ b/bdd/utils.go @@ -82,6 +82,7 @@ func (c *dbContext) resetBeforeScenario(ctx context.Context, _ *godog.Scenario) } func (c *dbContext) closeDBSession(ctx context.Context, _ *godog.Scenario, _ error) (context.Context, error) { + c.resetBeforeScenario(ctx, nil) if c.db == nil { return ctx, nil }