From 0743b6d2862af32728c0ec7fb1b7410c8709a84e Mon Sep 17 00:00:00 2001 From: Lance Bragstad Date: Wed, 20 Jul 2022 15:20:53 -0500 Subject: [PATCH] Implement a foreign key for subjects We want to be able to nest subjects and build a hierarchical relationship between them. This commit does that by adding a database migration that adds a new column for parent_id and adds a FK relationship to it. This approach was discussed in https://github.com/rhmdnd/compserv/issues/84 --- .../000002_add_subject_parent_id.down.sql | 0 .../000002_add_subject_parent_id.up.sql | 5 ++ tests/integration/helpers.go | 9 ++- tests/integration/integration_test.go | 66 ++++++++++++++++++- 4 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 migrations/000002_add_subject_parent_id.down.sql create mode 100644 migrations/000002_add_subject_parent_id.up.sql diff --git a/migrations/000002_add_subject_parent_id.down.sql b/migrations/000002_add_subject_parent_id.down.sql new file mode 100644 index 00000000..e69de29b diff --git a/migrations/000002_add_subject_parent_id.up.sql b/migrations/000002_add_subject_parent_id.up.sql new file mode 100644 index 00000000..04a93fd2 --- /dev/null +++ b/migrations/000002_add_subject_parent_id.up.sql @@ -0,0 +1,5 @@ +ALTER TABLE subjects +ADD COLUMN parent_id UUID; + +ALTER TABLE subjects +ADD CONSTRAINT fk_subjects FOREIGN KEY (parent_id) REFERENCES subjects (id); diff --git a/tests/integration/helpers.go b/tests/integration/helpers.go index d4571b02..126e8ccd 100644 --- a/tests/integration/helpers.go +++ b/tests/integration/helpers.go @@ -1,7 +1,10 @@ package integration_test // nolint:typecheck +import "database/sql" + type Subject struct { - ID string - Name string - Type string + ID string + Name string + Type string + ParentID sql.NullString } diff --git a/tests/integration/integration_test.go b/tests/integration/integration_test.go index 8336131a..f00ccc9b 100644 --- a/tests/integration/integration_test.go +++ b/tests/integration/integration_test.go @@ -115,6 +115,10 @@ func TestInsertSubjectSucceeds(t *testing.T) { // nolint:paralleltest // databas assert.Equal(t, expectedSubject.ID, subject.ID, "expected %s got %s", expectedSubject.ID, subject.ID) assert.Equal(t, expectedSubject.Name, subject.Name, "expected %s got %s", expectedSubject.Name, subject.Name) assert.Equal(t, expectedSubject.Type, subject.Type, "expected %s got %s", expectedSubject.Type, subject.Type) + parentIDValue, err := subject.ParentID.Value() + assert.False(t, subject.ParentID.Valid) + assert.Nil(t, err) + assert.Nil(t, parentIDValue) // Drop the database instead of downgrading since we don't need the // data anyway @@ -187,6 +191,66 @@ func TestInsertSubjectWithLongTypeFails(t *testing.T) { // nolint:paralleltest / } } +func TestInsertSubjectWithParent(t *testing.T) { // nolint:paralleltest // database tests should run serially + m := getMigrationHelper(t) + if err := m.Up(); err != nil { + t.Fatalf("Unable to upgrade database: %s", err) + } + + // Create a subject to act as the parent + parentID := getUUIDString() + subjectTypeStr := getUUIDString() + p := Subject{ID: parentID, Name: clusterName, Type: subjectTypeStr} + gormDB := getGormHelper() + gormDB.Create(&p) + + // Create another subject referencing the parent + id := getUUIDString() + subjectTypeStr = getUUIDString() + s := Subject{ID: id, Name: clusterName, Type: subjectTypeStr, ParentID: sql.NullString{String: parentID, Valid: true}} + gormDB.Create(&s) + + a := Subject{} + gormDB.First(&a, "id = ?", id) + + e := Subject{ID: id, Name: clusterName, Type: subjectTypeStr, ParentID: sql.NullString{String: parentID, Valid: true}} + assert.Equal(t, e.ID, a.ID, "expected %s got %s", e.ID, a.ID) + assert.Equal(t, e.Name, a.Name, "expected %s got %s", e.Name, a.Name) + assert.Equal(t, e.Type, a.Type, "expected %s got %s", e.Type, a.Type) + assert.True(t, a.ParentID.Valid) + assert.Equal(t, e.ParentID.String, a.ParentID.String, "expected %s got %s", e.ParentID.String, a.ParentID.String) + + // Drop the database instead of downgrading since we don't need the + // data anyway + if err := m.Drop(); err != nil { + t.Fatalf("Unable to drop database: %s", err) + } +} + +func TestInsertSubjectWithNonExistentParent(t *testing.T) { // nolint:paralleltest // database tests should run serially + m := getMigrationHelper(t) + if err := m.Up(); err != nil { + t.Fatalf("Unable to upgrade database: %s", err) + } + + // Generate a fake parent ID that doesn't actually exist + parentID := getUUIDString() + + // Create another subject referencing the parent + id := getUUIDString() + subjectTypeStr := getUUIDString() + s := Subject{ID: id, Name: clusterName, Type: subjectTypeStr, ParentID: sql.NullString{String: parentID, Valid: true}} + gormDB := getGormHelper() + err := gormDB.Create(&s).Error + assert.NotEmpty(t, err, "Shouldn't be able to insert values the violate foreign key constraints") + + // Drop the database instead of downgrading since we don't need the + // data anyway + if err := m.Drop(); err != nil { + t.Fatalf("Unable to drop database: %s", err) + } +} + func TestMigration(t *testing.T) { // nolint:paralleltest // database tests should run serially m := getMigrationHelper(t) @@ -205,7 +269,7 @@ func TestMigration(t *testing.T) { // nolint:paralleltest // database tests shou // Upgrade the database and make sure all upgrades apply cleanly. err = m.Up() version, dirty, _ = m.Version() - expectedVersion = uint(1) + expectedVersion = uint(2) assert.Equal(t, expectedVersion, version, "Database version mismatch: want %d but got %d", expectedVersion, version) assert.Equal(t, false, dirty, "Database state mismatch: want %t but got %t", false, dirty) assert.Equal(t, err, nil, "Error upgrading the database: %s", err)