Skip to content
Open
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
43 changes: 9 additions & 34 deletions bdd/steps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,33 +20,17 @@
package bdd

import (
"context"
"log"
"os"
"testing"

"github.com/cucumber/godog"
"github.com/specterops/dawgs/graph"
"github.com/specterops/dawgs/integration"
)

var (
bddDawgsFailed bool
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)
Expand All @@ -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,
},
}

Expand Down
41 changes: 37 additions & 4 deletions bdd/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand All @@ -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
Expand All @@ -50,15 +52,46 @@ 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) {
if err := c.anEmptyGraph(ctx); err != nil {
return ctx, fmt.Errorf("reset graph before scenario: %w", err)
}

c.db = nil
c.beforeExecution = graphSnapshot{}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
c.actualResult = nil
c.rowCount = 0
c.actualRows = nil

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)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
}

c.db = session.DB

if err := c.anEmptyGraph(ctx); err != nil {
return ctx, fmt.Errorf("reset graph before scenario: %w", err)
}
return ctx, nil
}

func (c *dbContext) closeDBSession(ctx context.Context, _ *godog.Scenario, _ error) (context.Context, error) {
c.resetBeforeScenario(ctx, 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
}

Expand Down
98 changes: 89 additions & 9 deletions bdd/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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)
Expand All @@ -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, graphSnapshot{}, databaseContext.beforeExecution)
require.Nil(t, databaseContext.actualResult)
require.Zero(t, databaseContext.rowCount)
require.Nil(t, 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.Nil(t, databaseContext.db)
require.False(t, database.writeTransactionCalled)
require.Equal(t, []string{"existing fixture"}, databaseContext.testData)
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) {
Expand Down
Loading