From be99fcd47cfb6fdeeb0f0c26c8bddbe922d5bb70 Mon Sep 17 00:00:00 2001 From: Mosen Date: Sun, 17 Jul 2016 14:48:12 +1000 Subject: [PATCH] Further comments on applications datastore. Attempt to make some where clause helper functions. Expand test suites to cover many different type of installed applications. All tests passing. --- applications/datastore.go | 71 +++++++++++++++++++- applications/datastore_test.go | 117 ++++++++++++++++++--------------- 2 files changed, 135 insertions(+), 53 deletions(-) diff --git a/applications/datastore.go b/applications/datastore.go index 92528304..847571aa 100644 --- a/applications/datastore.go +++ b/applications/datastore.go @@ -78,6 +78,9 @@ func (p Version) where() string { return fmt.Sprintf("version = '%s'", p.Version) } +// This function inserts a new application into the applications table. +// Applications are uniquely identifier by both their name and their long form version because some do not have +// identifiers, and some do not have short versions. func (store pgStore) New(a *Application) (string, error) { err := store.QueryRow( `INSERT INTO applications ( @@ -113,6 +116,7 @@ func (store pgStore) New(a *Application) (string, error) { return a.UUID, nil } +// Retrieve a list of applications func (store pgStore) Applications(params ...interface{}) ([]Application, error) { stmt := `SELECT * FROM applications` stmt = addWhereFilters(stmt, "OR", params...) @@ -126,8 +130,9 @@ func (store pgStore) Applications(params ...interface{}) ([]Application, error) return apps, nil } +// Retrieve only applications which are installed on the given device. func (store pgStore) GetApplicationsByDeviceUUID(deviceUUID string) (*[]Application, error) { - apps := []Application{} + var apps []Application query := `SELECT * FROM applications RIGHT JOIN devices_applications ON applications.application_uuid = devices_applications.application_uuid WHERE devices_applications.device_uuid=$1` @@ -171,3 +176,67 @@ func addWhereFilters(stmt string, separator string, params ...interface{}) strin } return stmt } + +// boolean operators are applied to where conditions which are part of a whereClauseGroup +type booleanOperator string + +const ( + OR = "OR" + AND = "AND" +) + +type whereClauseGroup struct { + Operator booleanOperator + Clauses []whereClause +} + +// Get a string representing the where clause +// Second return value is an array of arguments to give to db.Exec etc. +func (cg whereClauseGroup) String() (string, []string) { + var clauses []string + var values []string = make([]string, len(cg.Clauses)) + + for i, c := range cg.Clauses { + c.Placeholder = fmt.Sprintf("$%d", i) + clauses = append(clauses, c.String()) + values = append(values, c.Value) + } + + return strings.Join(clauses, string(cg.Operator)), values +} + +// Struct representation of a where clause. Does not deal with field name escaping or any inference of the value. +// I.E Do your own quoting. +type whereClause struct { + Operator string + Field string + Value string + Placeholder string +} + +func (c whereClause) String() string { + return fmt.Sprintf(`%s %s %s`, c.Field, c.Operator, c.Value) +} + +func Where(field string, operator string, value string) whereClause { + return whereClause{ + Operator: operator, + Field: field, + Value: value, + Placeholder: "$1", + } +} + +func WhereAnd(clauses ...whereClause) whereClauseGroup { + return whereClauseGroup{ + Operator: "AND", + Clauses: clauses, + } +} + +func WhereOr(clauses ...whereClause) whereClauseGroup { + return whereClauseGroup{ + Operator: "OR", + Clauses: clauses, + } +} diff --git a/applications/datastore_test.go b/applications/datastore_test.go index 0aaa8ceb..59bbc12f 100644 --- a/applications/datastore_test.go +++ b/applications/datastore_test.go @@ -11,9 +11,40 @@ import ( const MockUUID string = "ABCD-EFGH-IJKL" const MockName string = "Mock Application" +var appFixtures []Application = []Application{ + { // Normal macOS Application + UUID: "aba03d9d-6d80-4b96-bc6d-04233bddb26d", + Name: "Keychain Access", + Identifier: sql.NullString{"com.apple.keychainaccess", true}, + ShortVersion: sql.NullString{"9.0", true}, + Version: sql.NullString{"9.0", true}, + BundleSize: sql.NullInt64{14166172, true}, + }, + { // macOS Application with no versioning available + UUID: "ddacb35f-6a6a-42eb-8ce6-aad55da5a237", + Name: "unetbootin", + Identifier: sql.NullString{"com.yourcompany.unetbootin", true}, + BundleSize: sql.NullInt64{22292686, true}, + }, + { // macOS Application with no bundle size available + UUID: "cdd950a1-f596-4777-a63a-9839c28e4d48", + Name: "FileMerge", + Identifier: sql.NullString{"com.apple.FileMerge", true}, + ShortVersion: sql.NullString{"2.9.1", true}, + Version: sql.NullString{"2.9.1", true}, + }, + { // macOS Application with no bundle identifier + UUID: "84c78174-8331-4cf3-98c3-a4b1434617e5", + Name: "Wireless Network Utility", + BundleSize: sql.NullInt64{2416111, true}, + }, +} + +var logger log.Logger = log.NewNopLogger() + //func TestNewDB(t *testing.T) { -// var log log.Logger = log.NewNopLogger() -// appsDB, err := NewDB("postgres", "host=localhost", log) +// var logger logger.Logger = logger.NewNopLogger() +// appsDB, err := NewDB("postgres", "host=localhost", logger) // // if err != nil { // t.Error(err) @@ -26,7 +57,6 @@ const MockName string = "Mock Application" //} func TestNewDatastore(t *testing.T) { - var log log.Logger = log.NewNopLogger() db, _, err := sqlmock.New() if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) @@ -34,13 +64,12 @@ func TestNewDatastore(t *testing.T) { dbx := sqlx.NewDb(db, "mock") defer dbx.Close() - if _, err := NewDatastore(dbx, log); err != nil { + if _, err := NewDatastore(dbx, logger); err != nil { t.Error(err) } } func TestNewApplication(t *testing.T) { - var log log.Logger = log.NewNopLogger() db, mock, err := sqlmock.New() if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) @@ -48,38 +77,31 @@ func TestNewApplication(t *testing.T) { dbx := sqlx.NewDb(db, "mock") defer dbx.Close() - appsDs, err := NewDatastore(dbx, log) + appsDs, err := NewDatastore(dbx, logger) if err != nil { t.Error(err) } - // macOS style: no DynamicSize, no IsValidated - fixture := Application{ - Name: "Keychain Access", - Identifier: sql.NullString{"com.apple.keychainaccess", true}, - ShortVersion: sql.NullString{"9.0", true}, - Version: sql.NullString{"9.0", true}, - BundleSize: sql.NullInt64{14166172, true}, - } + for _, fixture := range appFixtures { + newRow := sqlmock.NewRows([]string{"application_uuid"}).AddRow(MockUUID) + mock.ExpectQuery("INSERT INTO applications").WithArgs( + fixture.Name, + fixture.Identifier, + fixture.ShortVersion, + fixture.Version, + fixture.BundleSize, + nil, + nil, + ).WillReturnRows(newRow) - newRow := sqlmock.NewRows([]string{"application_uuid"}).AddRow(MockUUID) - mock.ExpectQuery("INSERT INTO applications").WithArgs( - fixture.Name, - fixture.Identifier.String, - fixture.ShortVersion.String, - fixture.Version.String, - fixture.BundleSize.Int64, - nil, - nil, - ).WillReturnRows(newRow) + appUuid, err := appsDs.New(&fixture) + if err != nil { + t.Error(err) + } - appUuid, err := appsDs.New(&fixture) - if err != nil { - t.Error(err) - } - - if appUuid != MockUUID { - t.Errorf("inserting a mock application did not return the mock uuid, got: %s", appUuid) + if appUuid != MockUUID { + t.Errorf("inserting a mock application did not return the mock uuid, got: %s", appUuid) + } } if err := mock.ExpectationsWereMet(); err != nil { @@ -88,7 +110,6 @@ func TestNewApplication(t *testing.T) { } func TestApplications(t *testing.T) { - var log log.Logger = log.NewNopLogger() db, mock, err := sqlmock.New() if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) @@ -96,7 +117,7 @@ func TestApplications(t *testing.T) { dbx := sqlx.NewDb(db, "mock") defer dbx.Close() - appsDs, err := NewDatastore(dbx, log) + appsDs, err := NewDatastore(dbx, logger) if err != nil { t.Error(err) } @@ -115,7 +136,6 @@ func TestApplications(t *testing.T) { } func TestApplicationsWhereUUID(t *testing.T) { - var log log.Logger = log.NewNopLogger() db, mock, err := sqlmock.New() if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) @@ -123,13 +143,13 @@ func TestApplicationsWhereUUID(t *testing.T) { dbx := sqlx.NewDb(db, "mock") defer dbx.Close() - appsDs, err := NewDatastore(dbx, log) + appsDs, err := NewDatastore(dbx, logger) if err != nil { t.Error(err) } mockRow := sqlmock.NewRows([]string{"application_uuid"}).AddRow(MockUUID) - mock.ExpectQuery(`WHERE application_uuid =`).WithArgs(MockUUID).WillReturnRows(mockRow) + mock.ExpectQuery(`WHERE application_uuid =`).WillReturnRows(mockRow) apps, err := appsDs.Applications(UUID{MockUUID}) if err != nil { @@ -150,7 +170,6 @@ func TestApplicationsWhereUUID(t *testing.T) { } func TestApplicationsWhereName(t *testing.T) { - var log log.Logger = log.NewNopLogger() db, mock, err := sqlmock.New() if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) @@ -158,13 +177,13 @@ func TestApplicationsWhereName(t *testing.T) { dbx := sqlx.NewDb(db, "mock") defer dbx.Close() - appsDs, err := NewDatastore(dbx, log) + appsDs, err := NewDatastore(dbx, logger) if err != nil { t.Error(err) } mockRow := sqlmock.NewRows([]string{"application_uuid", "name"}).AddRow(MockUUID, MockName) - mock.ExpectQuery(`WHERE name =`).WithArgs(MockName).WillReturnRows(mockRow) + mock.ExpectQuery(`WHERE name =`).WillReturnRows(mockRow) apps, err := appsDs.Applications(Name{MockName}) if err != nil { @@ -185,7 +204,6 @@ func TestApplicationsWhereName(t *testing.T) { } func TestGetApplicationsByDeviceUUID(t *testing.T) { - var log log.Logger = log.NewNopLogger() db, mock, err := sqlmock.New() if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) @@ -193,7 +211,7 @@ func TestGetApplicationsByDeviceUUID(t *testing.T) { dbx := sqlx.NewDb(db, "mock") defer dbx.Close() - appsDs, err := NewDatastore(dbx, log) + appsDs, err := NewDatastore(dbx, logger) if err != nil { t.Error(err) } @@ -211,7 +229,6 @@ func TestGetApplicationsByDeviceUUID(t *testing.T) { } func TestSaveApplicationByDeviceUUID(t *testing.T) { - var log log.Logger = log.NewNopLogger() db, mock, err := sqlmock.New() if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) @@ -219,20 +236,16 @@ func TestSaveApplicationByDeviceUUID(t *testing.T) { dbx := sqlx.NewDb(db, "mock") defer dbx.Close() - appsDs, err := NewDatastore(dbx, log) + appsDs, err := NewDatastore(dbx, logger) if err != nil { t.Error(err) } - fixture := Application{ - Name: "Keychain Access", - Identifier: sql.NullString{"com.apple.keychainaccess", true}, - ShortVersion: sql.NullString{"9.0", true}, - Version: sql.NullString{"9.0", true}, - BundleSize: sql.NullInt64{14166172, true}, - } - if err := appsDs.SaveApplicationByDeviceUUID(MockUUID, &fixture); err != nil { - t.Error(err) + for _, fixture := range appFixtures { + mock.ExpectExec("INSERT INTO devices_applications").WithArgs(MockUUID, fixture.UUID).WillReturnResult(sqlmock.NewResult(1, 1)) + if err := appsDs.SaveApplicationByDeviceUUID(MockUUID, &fixture); err != nil { + t.Error(err) + } } if err := mock.ExpectationsWereMet(); err != nil {