// VulcanizeDB // Copyright © 2019 Vulcanize // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . package event_test import ( "fmt" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/vulcanize/vulcanizedb/libraries/shared/factories/event" "github.com/vulcanize/vulcanizedb/libraries/shared/test_data" "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres/repositories" "github.com/vulcanize/vulcanizedb/pkg/fakes" "github.com/vulcanize/vulcanizedb/test_config" "math/big" ) var _ = Describe("Repository", func() { var db *postgres.DB BeforeEach(func() { db = test_config.NewTestDB(test_config.NewTestNode()) test_config.CleanTestDB(db) }) Describe("Create", func() { const createTestEventTableQuery = `CREATE TABLE public.testEvent( id SERIAL PRIMARY KEY, header_id INTEGER NOT NULL REFERENCES headers (id) ON DELETE CASCADE, log_id BIGINT NOT NULL REFERENCES header_sync_logs (id) ON DELETE CASCADE, variable1 TEXT, UNIQUE (header_id, log_id) );` var ( headerID, logID int64 headerRepository repositories.HeaderRepository testModel event.InsertionModel ) BeforeEach(func() { _, tableErr := db.Exec(createTestEventTableQuery) Expect(tableErr).NotTo(HaveOccurred()) headerRepository = repositories.NewHeaderRepository(db) var insertHeaderErr error headerID, insertHeaderErr = headerRepository.CreateOrUpdateHeader(fakes.FakeHeader) Expect(insertHeaderErr).NotTo(HaveOccurred()) headerSyncLog := test_data.CreateTestLog(headerID, db) logID = headerSyncLog.ID testModel = event.InsertionModel{ SchemaName: "public", TableName: "testEvent", OrderedColumns: []event.ColumnName{ event.HeaderFK, event.LogFK, "variable1", }, ColumnValues: event.ColumnValues{ event.HeaderFK: headerID, event.LogFK: logID, "variable1": "value1", }, } }) AfterEach(func() { db.MustExec(`DROP TABLE public.testEvent;`) }) // Needs to run before the other tests, since those insert keys in map It("memoizes queries", func() { Expect(len(event.ModelToQuery)).To(Equal(0)) event.GetMemoizedQuery(testModel) Expect(len(event.ModelToQuery)).To(Equal(1)) event.GetMemoizedQuery(testModel) Expect(len(event.ModelToQuery)).To(Equal(1)) }) It("persists a model to postgres", func() { createErr := event.Create([]event.InsertionModel{testModel}, db) Expect(createErr).NotTo(HaveOccurred()) var res TestEvent dbErr := db.Get(&res, `SELECT log_id, variable1 FROM public.testEvent;`) Expect(dbErr).NotTo(HaveOccurred()) Expect(res.LogID).To(Equal(fmt.Sprint(testModel.ColumnValues[event.LogFK]))) Expect(res.Variable1).To(Equal(testModel.ColumnValues["variable1"])) }) Describe("returns errors", func() { It("for empty model slice", func() { err := event.Create([]event.InsertionModel{}, db) Expect(err).To(MatchError("repository got empty model slice")) }) It("for failed SQL inserts", func() { header := fakes.GetFakeHeader(1) headerID, headerErr := headerRepository.CreateOrUpdateHeader(header) Expect(headerErr).NotTo(HaveOccurred()) brokenModel := event.InsertionModel{ SchemaName: "public", TableName: "testEvent", // Wrong name of last column compared to DB, will generate incorrect query OrderedColumns: []event.ColumnName{ event.HeaderFK, event.LogFK, "variable2", }, ColumnValues: event.ColumnValues{ event.HeaderFK: headerID, event.LogFK: logID, "variable1": "value1", }, } // Remove cached queries, or we won't generate a new (incorrect) one delete(event.ModelToQuery, "publictestEvent") createErr := event.Create([]event.InsertionModel{brokenModel}, db) // Remove incorrect query, so other tests won't get it delete(event.ModelToQuery, "publictestEvent") Expect(createErr).To(HaveOccurred()) }) It("for unsupported types in ColumnValue", func() { unsupportedValue := big.NewInt(5) testModel = event.InsertionModel{ SchemaName: "public", TableName: "testEvent", OrderedColumns: []event.ColumnName{ event.HeaderFK, event.LogFK, "variable1", }, ColumnValues: event.ColumnValues{ event.HeaderFK: headerID, event.LogFK: logID, "variable1": unsupportedValue, }, } createErr := event.Create([]event.InsertionModel{testModel}, db) Expect(createErr).To(MatchError(event.ErrUnsupportedValue(unsupportedValue))) }) }) It("upserts queries with conflicting source", func() { conflictingModel := event.InsertionModel{ SchemaName: "public", TableName: "testEvent", OrderedColumns: []event.ColumnName{ event.HeaderFK, event.LogFK, "variable1", }, ColumnValues: event.ColumnValues{ event.HeaderFK: headerID, event.LogFK: logID, "variable1": "conflictingValue", }, } createErr := event.Create([]event.InsertionModel{testModel, conflictingModel}, db) Expect(createErr).NotTo(HaveOccurred()) var res TestEvent dbErr := db.Get(&res, `SELECT log_id, variable1 FROM public.testEvent;`) Expect(dbErr).NotTo(HaveOccurred()) Expect(res.Variable1).To(Equal(conflictingModel.ColumnValues["variable1"])) }) It("generates correct queries", func() { actualQuery := event.GenerateInsertionQuery(testModel) expectedQuery := `INSERT INTO public.testEvent (header_id, log_id, variable1) VALUES($1, $2, $3) ON CONFLICT (header_id, log_id) DO UPDATE SET header_id = $1, log_id = $2, variable1 = $3;` Expect(actualQuery).To(Equal(expectedQuery)) }) It("marks log transformed", func() { createErr := event.Create([]event.InsertionModel{testModel}, db) Expect(createErr).NotTo(HaveOccurred()) var logTransformed bool getErr := db.Get(&logTransformed, `SELECT transformed FROM public.header_sync_logs WHERE id = $1`, logID) Expect(getErr).NotTo(HaveOccurred()) Expect(logTransformed).To(BeTrue()) }) }) }) type TestEvent struct { LogID string `db:"log_id"` Variable1 string }