diff --git a/dev/ci/go-test.sh b/dev/ci/go-test.sh index cdb41eeeeb90..d218eaead4ad 100755 --- a/dev/ci/go-test.sh +++ b/dev/ci/go-test.sh @@ -10,6 +10,10 @@ echo "--- build libsqlite" echo "--- comby install" ./dev/comby-install-or-upgrade.sh +# For code insights test +./dev/codeinsights-db.sh & +export CODEINSIGHTS_PGDATASOURCE=postgres://postgres:password@127.0.0.1:5435 + # Separate out time for go mod from go test echo "--- go mod download" go mod download diff --git a/enterprise/internal/insights/insights.go b/enterprise/internal/insights/insights.go index b057410eccb8..b64c20555c13 100644 --- a/enterprise/internal/insights/insights.go +++ b/enterprise/internal/insights/insights.go @@ -19,11 +19,12 @@ func Init(ctx context.Context, enterpriseServices *enterprise.Services) error { // TimescaleDB in those deployments. https://github.com/sourcegraph/sourcegraph/issues/17218 return nil } - _, err := initializeCodeInsightsDB() + timescale, err := initializeCodeInsightsDB() if err != nil { return err } - enterpriseServices.InsightsResolver = resolvers.New() + postgres := dbconn.Global + enterpriseServices.InsightsResolver = resolvers.New(timescale, postgres) return nil } diff --git a/enterprise/internal/insights/resolvers/resolver.go b/enterprise/internal/insights/resolvers/resolver.go index 377553792d5d..f4660cba316d 100644 --- a/enterprise/internal/insights/resolvers/resolver.go +++ b/enterprise/internal/insights/resolvers/resolver.go @@ -5,14 +5,23 @@ import ( "errors" "github.com/sourcegraph/sourcegraph/cmd/frontend/graphqlbackend" + "github.com/sourcegraph/sourcegraph/enterprise/internal/campaigns/store" + "github.com/sourcegraph/sourcegraph/internal/database" + "github.com/sourcegraph/sourcegraph/internal/database/dbutil" ) // Resolver is the GraphQL resolver of all things related to Insights. -type Resolver struct{} +type Resolver struct { + store *store.Store + settingStore *database.SettingStore +} -// New returns a new Resolver whose store uses the given database -func New() graphqlbackend.InsightsResolver { - return &Resolver{} +// New returns a new Resolver whose store uses the given Timescale and Postgres DBs. +func New(timescale, postgres dbutil.DB) graphqlbackend.InsightsResolver { + return &Resolver{ + store: store.New(timescale), + settingStore: database.Settings(postgres), + } } func (r *Resolver) Insights(ctx context.Context) (graphqlbackend.InsightsResolver, error) { diff --git a/enterprise/internal/insights/store/insights_test.go b/enterprise/internal/insights/store/insights_test.go new file mode 100644 index 000000000000..ae5e39192d17 --- /dev/null +++ b/enterprise/internal/insights/store/insights_test.go @@ -0,0 +1,12 @@ +package store + +import ( + "context" + "testing" + "time" +) + +func testInsights(t *testing.T, ctx context.Context, s *Store, clock func() time.Time) { + // TODO: write tests against the store once it is implemented + // https://github.com/sourcegraph/sourcegraph/issues/17218 +} diff --git a/enterprise/internal/insights/store/integration_test.go b/enterprise/internal/insights/store/integration_test.go new file mode 100644 index 000000000000..f314f3a16766 --- /dev/null +++ b/enterprise/internal/insights/store/integration_test.go @@ -0,0 +1,54 @@ +package store + +import ( + "context" + "database/sql" + "os" + "os/user" + "strings" + "testing" + + "github.com/sourcegraph/sourcegraph/internal/database/dbconn" + "github.com/sourcegraph/sourcegraph/internal/database/dbutil" + "github.com/sourcegraph/sourcegraph/internal/timeutil" +) + +func TestIntegration(t *testing.T) { + if testing.Short() { + t.Skip() + } + + t.Parallel() + + getTimescaleDB := func(t testing.TB) *sql.DB { + // Setup TimescaleDB for testing. + username := "" + if user, err := user.Current(); err == nil { + username = user.Username + } + timescaleDSN := dbutil.PostgresDSN("codeinsights", username, os.Getenv) + db, err := dbconn.New(timescaleDSN, "insights-test-"+strings.Replace(t.Name(), "/", "_", -1)) + if err != nil { + t.Log("") + t.Log("README: To run these tests you need to have the codeinsights TimescaleDB running:") + t.Log("") + t.Log("$ ./dev/codeinsights-db.sh &") + t.Log("$ export CODEINSIGHTS_PGDATASOURCE=postgres://postgres:password@127.0.0.1:5435") + t.Log("") + t.Log("Or skip them with 'go test -short'") + t.Log("") + t.Fatalf("Failed to connect to codeinsights database: %s", err) + } + if err := dbconn.MigrateDB(db, dbconn.CodeInsights); err != nil { + t.Fatalf("Failed to perform codeinsights database migration: %s", err) + } + return db + } + + t.Run("Integration", func(t *testing.T) { + ctx := context.Background() + clock := timeutil.Now + store := NewWithClock(getTimescaleDB(t), clock) + t.Run("Insights", func(t *testing.T) { testInsights(t, ctx, store, clock) }) + }) +} diff --git a/enterprise/internal/insights/store/store.go b/enterprise/internal/insights/store/store.go new file mode 100644 index 000000000000..fc9a98f4bb04 --- /dev/null +++ b/enterprise/internal/insights/store/store.go @@ -0,0 +1,41 @@ +package store + +import ( + "database/sql" + "time" + + "github.com/sourcegraph/sourcegraph/internal/database/basestore" + "github.com/sourcegraph/sourcegraph/internal/database/dbutil" + "github.com/sourcegraph/sourcegraph/internal/timeutil" +) + +// Store exposes methods to read and write code insights domain models from +// persistent storage. +type Store struct { + *basestore.Store + now func() time.Time +} + +// New returns a new Store backed by the given Timescale db. +func New(db dbutil.DB) *Store { + return NewWithClock(db, timeutil.Now) +} + +// NewWithClock returns a new Store backed by the given db and +// clock for timestamps. +func NewWithClock(db dbutil.DB, clock func() time.Time) *Store { + return &Store{Store: basestore.NewWithDB(db, sql.TxOptions{}), now: clock} +} + +var _ basestore.ShareableStore = &Store{} + +// Handle returns the underlying transactable database handle. +// Needed to implement the ShareableStore interface. +func (s *Store) Handle() *basestore.TransactableHandle { return s.Store.Handle() } + +// With creates a new Store with the given basestore.Shareable store as the +// underlying basestore.Store. +// Needed to implement the basestore.Store interface +func (s *Store) With(other basestore.ShareableStore) *Store { + return &Store{Store: s.Store.With(other), now: s.now} +}