Stefan McShane 2 vuotta sitten
vanhempi
sitoutus
7c26b0bf7e
42 muutettua tiedostoa jossa 3920 lisäystä ja 0 poistoa
  1. 2 0
      .github/golangci-lint.yaml
  2. 41 0
      internal/repository/test/allowlist.go
  3. 37 0
      internal/repository/test/api_contract.go
  4. 32 0
      internal/repository/test/api_token.go
  5. 24 0
      internal/repository/test/app_instance.go
  6. 28 0
      internal/repository/test/app_revision.go
  7. 28 0
      internal/repository/test/app_template.go
  8. 724 0
      internal/repository/test/auth.go
  9. 54 0
      internal/repository/test/auth_code.go
  10. 32 0
      internal/repository/test/aws_assume_role_chain.go
  11. 48 0
      internal/repository/test/build_config.go
  12. 205 0
      internal/repository/test/cluster.go
  13. 45 0
      internal/repository/test/cred_exchange_token.go
  14. 36 0
      internal/repository/test/database.go
  15. 44 0
      internal/repository/test/datastore.go
  16. 38 0
      internal/repository/test/deployment_target.go
  17. 36 0
      internal/repository/test/dns_record.go
  18. 86 0
      internal/repository/test/environment.go
  19. 75 0
      internal/repository/test/event.go
  20. 62 0
      internal/repository/test/git_action_config.go
  21. 35 0
      internal/repository/test/github_webhook.go
  22. 103 0
      internal/repository/test/gitrepo.go
  23. 126 0
      internal/repository/test/helm_repo.go
  24. 115 0
      internal/repository/test/infra.go
  25. 124 0
      internal/repository/test/invite.go
  26. 32 0
      internal/repository/test/monitor.go
  27. 42 0
      internal/repository/test/notification.go
  28. 75 0
      internal/repository/test/onboarding.go
  29. 40 0
      internal/repository/test/policy.go
  30. 50 0
      internal/repository/test/porter_app.go
  31. 64 0
      internal/repository/test/porter_app_event.go
  32. 229 0
      internal/repository/test/project.go
  33. 65 0
      internal/repository/test/pw_reset_token.go
  34. 131 0
      internal/repository/test/registry.go
  35. 131 0
      internal/repository/test/release.go
  36. 333 0
      internal/repository/test/repository.go
  37. 102 0
      internal/repository/test/session.go
  38. 24 0
      internal/repository/test/slack.go
  39. 68 0
      internal/repository/test/stack.go
  40. 40 0
      internal/repository/test/tag.go
  41. 129 0
      internal/repository/test/usage.go
  42. 185 0
      internal/repository/test/user.go

+ 2 - 0
.github/golangci-lint.yaml

@@ -4,6 +4,8 @@ run:
   issues-exit-code: 1
   build-tags:
     - codeanalysis
+  skip-dirs:
+    - internal/repository/test
 
 issues:
   new-from-rev: origin/master # default: HEAD, this will only show linting changes in the current change

+ 41 - 0
internal/repository/test/allowlist.go

@@ -0,0 +1,41 @@
+package test
+
+import (
+	"errors"
+
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+)
+
+// AllowlistRepository uses gorm.DB for querying the database
+type AllowlistRepository struct {
+	canQuery  bool
+	allowlist []*models.Allowlist
+}
+
+// NewAllowlistRepository returns a AllowListRepository which uses
+// gorm.DB for querying the database.
+func NewAllowlistRepository(canQuery bool) repository.AllowlistRepository {
+	return &AllowlistRepository{canQuery, []*models.Allowlist{}}
+}
+
+func (repo *AllowlistRepository) UserEmailExists(email string) (bool, error) {
+	if !repo.canQuery {
+		return false, errors.New("cannot read database")
+	}
+
+	if len(repo.allowlist) == 0 {
+		return false, nil
+	}
+
+	founded := false
+
+	for _, allowed := range repo.allowlist {
+		if allowed.UserEmail == email {
+			founded = true
+			break
+		}
+	}
+
+	return founded, nil
+}

+ 37 - 0
internal/repository/test/api_contract.go

@@ -0,0 +1,37 @@
+package test
+
+import (
+	"context"
+	"errors"
+
+	"github.com/google/uuid"
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+)
+
+// APIContractRepository uses gorm.DB for querying the database
+type APIContractRepository struct{}
+
+// NewAPIContractRevisioner creates an APIRevision connection
+func NewAPIContractRevisioner() repository.APIContractRevisioner {
+	return &APIContractRepository{}
+}
+
+// Insert creates a new record in the api_contract_revisions table
+func (cr APIContractRepository) Insert(ctx context.Context, conf models.APIContractRevision) (models.APIContractRevision, error) {
+	return conf, errors.New("not implemented")
+}
+
+// List returns a list of api contract revisions sorted by created date for a given project and cluster
+func (cr APIContractRepository) List(ctx context.Context, projectID uint, opts ...repository.APIContractRevisionFilters) ([]*models.APIContractRevision, error) {
+	var confs []*models.APIContractRevision
+	return confs, errors.New("not implemented")
+}
+
+func (cr APIContractRepository) Delete(ctx context.Context, projectID uint, clusterID uint, revisionID uuid.UUID) error {
+	return errors.New("not implemented")
+}
+
+func (cr APIContractRepository) Get(ctx context.Context, revisionID uuid.UUID) (models.APIContractRevision, error) {
+	return models.APIContractRevision{}, errors.New("not implemented")
+}

+ 32 - 0
internal/repository/test/api_token.go

@@ -0,0 +1,32 @@
+package test
+
+import (
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+)
+
+type APITokenRepository struct {
+	canQuery bool
+}
+
+func NewAPITokenRepository(canQuery bool) repository.APITokenRepository {
+	return &APITokenRepository{canQuery}
+}
+
+func (repo *APITokenRepository) CreateAPIToken(a *models.APIToken) (*models.APIToken, error) {
+	panic("unimplemented")
+}
+
+func (repo *APITokenRepository) ListAPITokensByProjectID(projectID uint) ([]*models.APIToken, error) {
+	panic("unimplemented")
+}
+
+func (repo *APITokenRepository) ReadAPIToken(projectID uint, uid string) (*models.APIToken, error) {
+	panic("unimplemented")
+}
+
+func (repo *APITokenRepository) UpdateAPIToken(
+	token *models.APIToken,
+) (*models.APIToken, error) {
+	panic("unimplemented")
+}

+ 24 - 0
internal/repository/test/app_instance.go

@@ -0,0 +1,24 @@
+package test
+
+import (
+	"context"
+	"errors"
+
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+)
+
+// AppInstanceRepository is a test repository that implements repository.AppInstanceRepository
+type AppInstanceRepository struct {
+	canQuery bool
+}
+
+// NewAppInstanceRepository returns the test AppInstanceRepository
+func NewAppInstanceRepository() repository.AppInstanceRepository {
+	return &AppInstanceRepository{canQuery: false}
+}
+
+// Get returns an app instance by its id
+func (repo *AppInstanceRepository) Get(ctx context.Context, id string) (*models.AppInstance, error) {
+	return nil, errors.New("cannot read database")
+}

+ 28 - 0
internal/repository/test/app_revision.go

@@ -0,0 +1,28 @@
+package test
+
+import (
+	"errors"
+
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+)
+
+// AppRevisionRepository is a test repository that implements repository.AppRevisionRepository
+type AppRevisionRepository struct {
+	canQuery bool
+}
+
+// NewAppRevisionRepository returns the test AppRevisionRepository
+func NewAppRevisionRepository() repository.AppRevisionRepository {
+	return &AppRevisionRepository{canQuery: false}
+}
+
+// AppRevisionByInstanceIDAndRevisionNumber finds an app revision by revision number
+func (repo *AppRevisionRepository) AppRevisionByInstanceIDAndRevisionNumber(projectID uint, appInstanceId string, revisionNumber uint) (*models.AppRevision, error) {
+	return nil, errors.New("cannot read database")
+}
+
+// LatestNumberedAppRevision finds the latest numbered app revision
+func (repo *AppRevisionRepository) LatestNumberedAppRevision(projectID uint, appInstanceId string) (*models.AppRevision, error) {
+	return nil, errors.New("cannot read database")
+}

+ 28 - 0
internal/repository/test/app_template.go

@@ -0,0 +1,28 @@
+package test
+
+import (
+	"errors"
+
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+)
+
+// AppTemplateRepository is a test repository that implements repository.AppTemplateRepository
+type AppTemplateRepository struct {
+	canQuery bool
+}
+
+// NewAppTemplateRepository returns the test AppTemplateRepository
+func NewAppTemplateRepository() repository.AppTemplateRepository {
+	return &AppTemplateRepository{canQuery: false}
+}
+
+// AppTemplateByPorterAppID finds an app template by its porter app id
+func (repo *AppTemplateRepository) AppTemplateByPorterAppID(projectID uint, appID uint) (*models.AppTemplate, error) {
+	return nil, errors.New("cannot read database")
+}
+
+// CreateAppTemplate creates a new app template
+func (repo *AppTemplateRepository) CreateAppTemplate(appTemplate *models.AppTemplate) (*models.AppTemplate, error) {
+	return nil, errors.New("cannot write database")
+}

+ 724 - 0
internal/repository/test/auth.go

@@ -0,0 +1,724 @@
+package test
+
+import (
+	"errors"
+
+	"github.com/porter-dev/porter/internal/repository"
+	"gorm.io/gorm"
+
+	ints "github.com/porter-dev/porter/internal/models/integrations"
+)
+
+// KubeIntegrationRepository implements repository.KubeIntegrationRepository
+type KubeIntegrationRepository struct {
+	canQuery         bool
+	kubeIntegrations []*ints.KubeIntegration
+}
+
+// NewKubeIntegrationRepository will return errors if canQuery is false
+func NewKubeIntegrationRepository(canQuery bool) repository.KubeIntegrationRepository {
+	return &KubeIntegrationRepository{
+		canQuery,
+		[]*ints.KubeIntegration{},
+	}
+}
+
+// CreateKubeIntegration creates a new kube auth mechanism
+func (repo *KubeIntegrationRepository) CreateKubeIntegration(
+	am *ints.KubeIntegration,
+) (*ints.KubeIntegration, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	repo.kubeIntegrations = append(repo.kubeIntegrations, am)
+	am.ID = uint(len(repo.kubeIntegrations))
+
+	return am, nil
+}
+
+// ReadKubeIntegration finds a kube auth mechanism by id
+func (repo *KubeIntegrationRepository) ReadKubeIntegration(
+	projectID, id uint,
+) (*ints.KubeIntegration, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	if int(id-1) >= len(repo.kubeIntegrations) || repo.kubeIntegrations[id-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(id - 1)
+	return repo.kubeIntegrations[index], nil
+}
+
+// ListKubeIntegrationsByProjectID finds all kube auth mechanisms
+// for a given project id
+func (repo *KubeIntegrationRepository) ListKubeIntegrationsByProjectID(
+	projectID uint,
+) ([]*ints.KubeIntegration, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	res := make([]*ints.KubeIntegration, 0)
+
+	for _, kubeAM := range repo.kubeIntegrations {
+		if kubeAM.ProjectID == projectID {
+			res = append(res, kubeAM)
+		}
+	}
+
+	return res, nil
+}
+
+// BasicIntegrationRepository implements repository.BasicIntegrationRepository
+type BasicIntegrationRepository struct {
+	canQuery          bool
+	basicIntegrations []*ints.BasicIntegration
+}
+
+// NewBasicIntegrationRepository will return errors if canQuery is false
+func NewBasicIntegrationRepository(canQuery bool) repository.BasicIntegrationRepository {
+	return &BasicIntegrationRepository{
+		canQuery,
+		[]*ints.BasicIntegration{},
+	}
+}
+
+// CreateBasicIntegration creates a new basic auth mechanism
+func (repo *BasicIntegrationRepository) CreateBasicIntegration(
+	am *ints.BasicIntegration,
+) (*ints.BasicIntegration, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	repo.basicIntegrations = append(repo.basicIntegrations, am)
+	am.ID = uint(len(repo.basicIntegrations))
+
+	return am, nil
+}
+
+// ReadBasicIntegration finds a basic auth mechanism by id
+func (repo *BasicIntegrationRepository) ReadBasicIntegration(
+	projectID, id uint,
+) (*ints.BasicIntegration, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	if int(id-1) >= len(repo.basicIntegrations) || repo.basicIntegrations[id-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(id - 1)
+	return repo.basicIntegrations[index], nil
+}
+
+// ListBasicIntegrationsByProjectID finds all basic auth mechanisms
+// for a given project id
+func (repo *BasicIntegrationRepository) ListBasicIntegrationsByProjectID(
+	projectID uint,
+) ([]*ints.BasicIntegration, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	res := make([]*ints.BasicIntegration, 0)
+
+	for _, basicAM := range repo.basicIntegrations {
+		if basicAM.ProjectID == projectID {
+			res = append(res, basicAM)
+		}
+	}
+
+	return res, nil
+}
+
+// DeleteBasicIntegration deletes a basic integration
+func (repo *BasicIntegrationRepository) DeleteBasicIntegration(
+	am *ints.BasicIntegration,
+) (*ints.BasicIntegration, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	var newInts []*ints.BasicIntegration
+
+	for _, basicInt := range repo.basicIntegrations {
+		if basicInt.ID != am.ID {
+			newInts = append(newInts, basicInt)
+		}
+	}
+
+	repo.basicIntegrations = newInts
+
+	return am, nil
+}
+
+// OIDCIntegrationRepository implements repository.OIDCIntegrationRepository
+type OIDCIntegrationRepository struct {
+	canQuery         bool
+	oidcIntegrations []*ints.OIDCIntegration
+}
+
+// NewOIDCIntegrationRepository will return errors if canQuery is false
+func NewOIDCIntegrationRepository(canQuery bool) repository.OIDCIntegrationRepository {
+	return &OIDCIntegrationRepository{
+		canQuery,
+		[]*ints.OIDCIntegration{},
+	}
+}
+
+// CreateOIDCIntegration creates a new oidc auth mechanism
+func (repo *OIDCIntegrationRepository) CreateOIDCIntegration(
+	am *ints.OIDCIntegration,
+) (*ints.OIDCIntegration, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	repo.oidcIntegrations = append(repo.oidcIntegrations, am)
+	am.ID = uint(len(repo.oidcIntegrations))
+
+	return am, nil
+}
+
+// ReadOIDCIntegration finds a oidc auth mechanism by id
+func (repo *OIDCIntegrationRepository) ReadOIDCIntegration(
+	projectID, id uint,
+) (*ints.OIDCIntegration, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	if int(id-1) >= len(repo.oidcIntegrations) || repo.oidcIntegrations[id-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(id - 1)
+	return repo.oidcIntegrations[index], nil
+}
+
+// ListOIDCIntegrationsByProjectID finds all oidc auth mechanisms
+// for a given project id
+func (repo *OIDCIntegrationRepository) ListOIDCIntegrationsByProjectID(
+	projectID uint,
+) ([]*ints.OIDCIntegration, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	res := make([]*ints.OIDCIntegration, 0)
+
+	for _, oidcAM := range repo.oidcIntegrations {
+		if oidcAM.ProjectID == projectID {
+			res = append(res, oidcAM)
+		}
+	}
+
+	return res, nil
+}
+
+// OAuthIntegrationRepository implements repository.OAuthIntegrationRepository
+type OAuthIntegrationRepository struct {
+	canQuery      bool
+	oIntegrations []*ints.OAuthIntegration
+}
+
+// NewOAuthIntegrationRepository will return errors if canQuery is false
+func NewOAuthIntegrationRepository(canQuery bool) repository.OAuthIntegrationRepository {
+	return &OAuthIntegrationRepository{
+		canQuery,
+		[]*ints.OAuthIntegration{},
+	}
+}
+
+// CreateOAuthIntegration creates a new o auth mechanism
+func (repo *OAuthIntegrationRepository) CreateOAuthIntegration(
+	am *ints.OAuthIntegration,
+) (*ints.OAuthIntegration, error) {
+	if !repo.canQuery {
+		return nil, errors.New("cannot write database")
+	}
+
+	repo.oIntegrations = append(repo.oIntegrations, am)
+	am.ID = uint(len(repo.oIntegrations))
+
+	return am, nil
+}
+
+// ReadOAuthIntegration finds a o auth mechanism by id
+func (repo *OAuthIntegrationRepository) ReadOAuthIntegration(
+	projectID, id uint,
+) (*ints.OAuthIntegration, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	if int(id-1) >= len(repo.oIntegrations) || repo.oIntegrations[id-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(id - 1)
+	return repo.oIntegrations[index], nil
+}
+
+// ListOAuthIntegrationsByProjectID finds all o auth mechanisms
+// for a given project id
+func (repo *OAuthIntegrationRepository) ListOAuthIntegrationsByProjectID(
+	projectID uint,
+) ([]*ints.OAuthIntegration, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	res := make([]*ints.OAuthIntegration, 0)
+
+	for _, oAM := range repo.oIntegrations {
+		if oAM.ProjectID == projectID {
+			res = append(res, oAM)
+		}
+	}
+
+	return res, nil
+}
+
+// UpdateOAuthIntegration updates an oauth integration in the DB
+func (repo *OAuthIntegrationRepository) UpdateOAuthIntegration(
+	am *ints.OAuthIntegration,
+) (*ints.OAuthIntegration, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	if int(am.ID-1) >= len(repo.oIntegrations) || repo.oIntegrations[am.ID-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(am.ID - 1)
+	repo.oIntegrations[index] = am
+
+	return am, nil
+}
+
+// AWSIntegrationRepository implements repository.AWSIntegrationRepository
+type AWSIntegrationRepository struct {
+	canQuery        bool
+	awsIntegrations []*ints.AWSIntegration
+}
+
+// NewAWSIntegrationRepository will return errors if canQuery is false
+func NewAWSIntegrationRepository(canQuery bool) repository.AWSIntegrationRepository {
+	return &AWSIntegrationRepository{
+		canQuery,
+		[]*ints.AWSIntegration{},
+	}
+}
+
+// CreateAWSIntegration creates a new aws auth mechanism
+func (repo *AWSIntegrationRepository) CreateAWSIntegration(
+	am *ints.AWSIntegration,
+) (*ints.AWSIntegration, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	repo.awsIntegrations = append(repo.awsIntegrations, am)
+	am.ID = uint(len(repo.awsIntegrations))
+
+	return am, nil
+}
+
+func (repo *AWSIntegrationRepository) OverwriteAWSIntegration(
+	am *ints.AWSIntegration,
+) (*ints.AWSIntegration, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	if int(am.ID-1) >= len(repo.awsIntegrations) || repo.awsIntegrations[am.ID-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(am.ID - 1)
+	repo.awsIntegrations[index] = am
+
+	return am, nil
+}
+
+// ReadAWSIntegration finds a aws auth mechanism by id
+func (repo *AWSIntegrationRepository) ReadAWSIntegration(
+	projectID, id uint,
+) (*ints.AWSIntegration, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	if int(id-1) >= len(repo.awsIntegrations) || repo.awsIntegrations[id-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(id - 1)
+	return repo.awsIntegrations[index], nil
+}
+
+// ListAWSIntegrationsByProjectID finds all aws auth mechanisms
+// for a given project id
+func (repo *AWSIntegrationRepository) ListAWSIntegrationsByProjectID(
+	projectID uint,
+) ([]*ints.AWSIntegration, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	res := make([]*ints.AWSIntegration, 0)
+
+	for _, awsAM := range repo.awsIntegrations {
+		if awsAM.ProjectID == projectID {
+			res = append(res, awsAM)
+		}
+	}
+
+	return res, nil
+}
+
+// GCPIntegrationRepository implements repository.GCPIntegrationRepository
+type GCPIntegrationRepository struct {
+	canQuery        bool
+	gcpIntegrations []*ints.GCPIntegration
+}
+
+// NewGCPIntegrationRepository will return errors if canQuery is false
+func NewGCPIntegrationRepository(canQuery bool) repository.GCPIntegrationRepository {
+	return &GCPIntegrationRepository{
+		canQuery,
+		[]*ints.GCPIntegration{},
+	}
+}
+
+// CreateGCPIntegration creates a new gcp auth mechanism
+func (repo *GCPIntegrationRepository) CreateGCPIntegration(
+	am *ints.GCPIntegration,
+) (*ints.GCPIntegration, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	repo.gcpIntegrations = append(repo.gcpIntegrations, am)
+	am.ID = uint(len(repo.gcpIntegrations))
+
+	return am, nil
+}
+
+// ReadGCPIntegration finds a gcp auth mechanism by id
+func (repo *GCPIntegrationRepository) ReadGCPIntegration(
+	projectID, id uint,
+) (*ints.GCPIntegration, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	if int(id-1) >= len(repo.gcpIntegrations) || repo.gcpIntegrations[id-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(id - 1)
+	return repo.gcpIntegrations[index], nil
+}
+
+// ListGCPIntegrationsByProjectID finds all gcp auth mechanisms
+// for a given project id
+func (repo *GCPIntegrationRepository) ListGCPIntegrationsByProjectID(
+	projectID uint,
+) ([]*ints.GCPIntegration, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	res := make([]*ints.GCPIntegration, 0)
+
+	for _, gcpAM := range repo.gcpIntegrations {
+		if gcpAM.ProjectID == projectID {
+			res = append(res, gcpAM)
+		}
+	}
+
+	return res, nil
+}
+
+// GithubAppInstallationRepository implements repository.GithubAppInstallationRepository
+type GithubAppInstallationRepository struct {
+	canQuery               bool
+	githubAppInstallations []*ints.GithubAppInstallation
+}
+
+func NewGithubAppInstallationRepository(canQuery bool) repository.GithubAppInstallationRepository {
+	return &GithubAppInstallationRepository{
+		canQuery,
+		[]*ints.GithubAppInstallation{},
+	}
+}
+
+func (repo *GithubAppInstallationRepository) CreateGithubAppInstallation(am *ints.GithubAppInstallation) (*ints.GithubAppInstallation, error) {
+	if !repo.canQuery {
+		return nil, errors.New("cannot write database")
+	}
+
+	repo.githubAppInstallations = append(repo.githubAppInstallations, am)
+	am.ID = uint(len(repo.githubAppInstallations))
+
+	return am, nil
+}
+
+func (repo *GithubAppInstallationRepository) ReadGithubAppInstallation(projectID, id uint) (*ints.GithubAppInstallation, error) {
+	if !repo.canQuery {
+		return nil, errors.New("cannot write database")
+	}
+
+	if int(id-1) >= len(repo.githubAppInstallations) || repo.githubAppInstallations[id-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	return repo.githubAppInstallations[int(id-1)], nil
+}
+
+func (repo *GithubAppInstallationRepository) ReadGithubAppInstallationByInstallationID(gaID uint) (*ints.GithubAppInstallation, error) {
+	panic("unimplemented")
+}
+
+func (repo *GithubAppInstallationRepository) ReadGithubAppInstallationByAccountID(accountID int64) (*ints.GithubAppInstallation, error) {
+	if !repo.canQuery {
+		return nil, errors.New("cannot write database")
+	}
+
+	for _, installation := range repo.githubAppInstallations {
+		if installation != nil && installation.AccountID == accountID {
+			return installation, nil
+		}
+	}
+
+	return nil, gorm.ErrRecordNotFound
+}
+
+func (repo *GithubAppInstallationRepository) ReadGithubAppInstallationByAccountIDs(accountIDs []int64) ([]*ints.GithubAppInstallation, error) {
+	if !repo.canQuery {
+		return nil, errors.New("cannot write database")
+	}
+
+	ret := make([]*ints.GithubAppInstallation, 0)
+
+	for _, installation := range repo.githubAppInstallations {
+		// O(n^2) can be made into O(n) if this is too slow
+		for _, id := range accountIDs {
+			if installation.AccountID == id {
+				ret = append(ret, installation)
+			}
+		}
+	}
+
+	return ret, nil
+}
+
+func (repo *GithubAppInstallationRepository) DeleteGithubAppInstallationByAccountID(accountID int64) error {
+	if !repo.canQuery {
+		return errors.New("cannot write database")
+	}
+
+	for i, installation := range repo.githubAppInstallations {
+		if installation != nil && installation.AccountID == accountID {
+			repo.githubAppInstallations[i] = nil
+		}
+	}
+
+	return nil
+}
+
+type GithubAppOAuthIntegrationRepository struct {
+	canQuery                   bool
+	githubAppOauthIntegrations []*ints.GithubAppOAuthIntegration
+}
+
+func NewGithubAppOAuthIntegrationRepository(canQuery bool) repository.GithubAppOAuthIntegrationRepository {
+	return &GithubAppOAuthIntegrationRepository{
+		canQuery,
+		[]*ints.GithubAppOAuthIntegration{},
+	}
+}
+
+func (repo *GithubAppOAuthIntegrationRepository) CreateGithubAppOAuthIntegration(am *ints.GithubAppOAuthIntegration) (*ints.GithubAppOAuthIntegration, error) {
+	if !repo.canQuery {
+		return nil, errors.New("cannot write database")
+	}
+
+	repo.githubAppOauthIntegrations = append(repo.githubAppOauthIntegrations, am)
+	am.ID = uint(len(repo.githubAppOauthIntegrations))
+
+	return am, nil
+}
+
+func (repo *GithubAppOAuthIntegrationRepository) ReadGithubAppOauthIntegration(id uint) (*ints.GithubAppOAuthIntegration, error) {
+	if !repo.canQuery {
+		return nil, errors.New("cannot write database")
+	}
+
+	if int(id-1) >= len(repo.githubAppOauthIntegrations) || repo.githubAppOauthIntegrations[id-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	return repo.githubAppOauthIntegrations[int(id-1)], nil
+}
+
+func (repo *GithubAppOAuthIntegrationRepository) UpdateGithubAppOauthIntegration(am *ints.GithubAppOAuthIntegration) (*ints.GithubAppOAuthIntegration, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	if int(am.ID-1) >= len(repo.githubAppOauthIntegrations) || repo.githubAppOauthIntegrations[am.ID-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(am.ID - 1)
+	repo.githubAppOauthIntegrations[index] = am
+
+	return am, nil
+}
+
+// AzureIntegrationRepository (unimplemented)
+type AzureIntegrationRepository struct{}
+
+// NewAzureIntegrationRepository returns a AzureIntegrationRepository which uses
+// gorm.DB for querying the database. It accepts an encryption key to encrypt
+// sensitive data
+func NewAzureIntegrationRepository() repository.AzureIntegrationRepository {
+	return &AzureIntegrationRepository{}
+}
+
+// CreateAzureIntegration creates a new Azure auth mechanism
+func (repo *AzureIntegrationRepository) CreateAzureIntegration(
+	az *ints.AzureIntegration,
+) (*ints.AzureIntegration, error) {
+	panic("unimplemented")
+}
+
+// OverwriteAzureIntegration overwrites the Azure credential in the DB
+func (repo *AzureIntegrationRepository) OverwriteAzureIntegration(
+	az *ints.AzureIntegration,
+) (*ints.AzureIntegration, error) {
+	panic("unimplemented")
+}
+
+// ReadAzureIntegration finds a Azure auth mechanism by id
+func (repo *AzureIntegrationRepository) ReadAzureIntegration(
+	projectID, id uint,
+) (*ints.AzureIntegration, error) {
+	panic("unimplemented")
+}
+
+// ListAzureIntegrationsByProjectID finds all Azure auth mechanisms
+// for a given project id
+func (repo *AzureIntegrationRepository) ListAzureIntegrationsByProjectID(
+	projectID uint,
+) ([]*ints.AzureIntegration, error) {
+	panic("unimplemented")
+}
+
+type GitlabIntegrationRepository struct {
+	canQuery           bool
+	gitlabIntegrations []*ints.GitlabIntegration
+}
+
+func NewGitlabIntegrationRepository(canQuery bool) repository.GitlabIntegrationRepository {
+	return &GitlabIntegrationRepository{
+		canQuery,
+		[]*ints.GitlabIntegration{},
+	}
+}
+
+func (repo *GitlabIntegrationRepository) CreateGitlabIntegration(gi *ints.GitlabIntegration) (*ints.GitlabIntegration, error) {
+	if !repo.canQuery {
+		return nil, errors.New("cannot write database")
+	}
+
+	repo.gitlabIntegrations = append(repo.gitlabIntegrations, gi)
+	gi.ID = uint(len(repo.gitlabIntegrations))
+
+	return gi, nil
+}
+
+func (repo *GitlabIntegrationRepository) ReadGitlabIntegration(projectID, id uint) (*ints.GitlabIntegration, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	if int(id-1) >= len(repo.gitlabIntegrations) || repo.gitlabIntegrations[id-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(id - 1)
+	return repo.gitlabIntegrations[index], nil
+}
+
+func (repo *GitlabIntegrationRepository) ListGitlabIntegrationsByProjectID(projectID uint) ([]*ints.GitlabIntegration, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	res := make([]*ints.GitlabIntegration, 0)
+
+	for _, gitlabAM := range repo.gitlabIntegrations {
+		if gitlabAM.ProjectID == projectID {
+			res = append(res, gitlabAM)
+		}
+	}
+
+	return res, nil
+}
+
+func (repo *GitlabIntegrationRepository) DeleteGitlabIntegrationByID(projectID, id uint) error {
+	if !repo.canQuery {
+		return errors.New("Cannot write database")
+	}
+
+	if int(id-1) >= len(repo.gitlabIntegrations) || repo.gitlabIntegrations[id-1] == nil || repo.gitlabIntegrations[id-1].ProjectID != projectID {
+		return gorm.ErrRecordNotFound
+	}
+
+	index := int(id - 1)
+	repo.gitlabIntegrations[index] = nil
+
+	return nil
+}
+
+type GitlabAppOAuthIntegrationRepository struct {
+	canQuery                   bool
+	gitlabAppOAuthIntegrations []*ints.GitlabAppOAuthIntegration
+}
+
+func NewGitlabAppOAuthIntegrationRepository(canQuery bool) repository.GitlabAppOAuthIntegrationRepository {
+	return &GitlabAppOAuthIntegrationRepository{
+		canQuery,
+		[]*ints.GitlabAppOAuthIntegration{},
+	}
+}
+
+func (repo *GitlabAppOAuthIntegrationRepository) CreateGitlabAppOAuthIntegration(
+	gi *ints.GitlabAppOAuthIntegration,
+) (*ints.GitlabAppOAuthIntegration, error) {
+	if !repo.canQuery {
+		return nil, errors.New("cannot write database")
+	}
+
+	repo.gitlabAppOAuthIntegrations = append(repo.gitlabAppOAuthIntegrations, gi)
+	gi.ID = uint(len(repo.gitlabAppOAuthIntegrations))
+
+	return gi, nil
+}
+
+func (repo *GitlabAppOAuthIntegrationRepository) ReadGitlabAppOAuthIntegration(
+	userID, projectID, integrationID uint,
+) (*ints.GitlabAppOAuthIntegration, error) {
+	panic("not implemented")
+}

+ 54 - 0
internal/repository/test/auth_code.go

@@ -0,0 +1,54 @@
+package test
+
+import (
+	"errors"
+
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+	"gorm.io/gorm"
+)
+
+// AuthCodeRepository uses gorm.DB for querying the database
+type AuthCodeRepository struct {
+	canQuery  bool
+	authCodes []*models.AuthCode
+}
+
+// NewAuthCodeRepository returns a AuthCodeRepository which uses
+// gorm.DB for querying the database
+func NewAuthCodeRepository(canQuery bool) repository.AuthCodeRepository {
+	return &AuthCodeRepository{canQuery, []*models.AuthCode{}}
+}
+
+// CreateAuthCode creates a new invite
+func (repo *AuthCodeRepository) CreateAuthCode(a *models.AuthCode) (*models.AuthCode, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	repo.authCodes = append(repo.authCodes, a)
+	a.ID = uint(len(repo.authCodes))
+
+	return a, nil
+}
+
+// ReadAuthCode gets an auth code object specified by the unique code
+func (repo *AuthCodeRepository) ReadAuthCode(code string) (*models.AuthCode, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	var res *models.AuthCode
+
+	for _, a := range repo.authCodes {
+		if code == a.AuthorizationCode {
+			res = a
+		}
+	}
+
+	if res == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	return res, nil
+}

+ 32 - 0
internal/repository/test/aws_assume_role_chain.go

@@ -0,0 +1,32 @@
+package test
+
+import (
+	"context"
+	"errors"
+
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+)
+
+// AWSAssumeRoleChain uses gorm.DB for querying the database
+type AWSAssumeRoleChain struct{}
+
+// NewAWSAssumeRoleChainer creates an AWSAssumeRoleChain connection
+func NewAWSAssumeRoleChainer() repository.AWSAssumeRoleChainer {
+	return &AWSAssumeRoleChain{}
+}
+
+// List returns a list of aws assume role chains for a given project, removing any chain links owned by Porter
+func (cr AWSAssumeRoleChain) List(ctx context.Context, projectID uint) ([]*models.AWSAssumeRoleChain, error) {
+	return nil, errors.New("not implemented")
+}
+
+// ListByAwsAccountId ...
+func (cr AWSAssumeRoleChain) ListByAwsAccountId(ctx context.Context, targetAwsAccountId string) ([]*models.AWSAssumeRoleChain, error) {
+	return nil, errors.New("not implemented")
+}
+
+// Delete ...
+func (cr AWSAssumeRoleChain) Delete(ctx context.Context, projectID uint) error {
+	return errors.New("not implemented")
+}

+ 48 - 0
internal/repository/test/build_config.go

@@ -0,0 +1,48 @@
+package test
+
+import (
+	"errors"
+
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+)
+
+type BuildConfigRepository struct {
+	canQuery     bool
+	buildConfigs []*models.BuildConfig
+}
+
+func NewBuildConfigRepository(canQuery bool) repository.BuildConfigRepository {
+	return &BuildConfigRepository{canQuery, []*models.BuildConfig{}}
+}
+
+func (repo *BuildConfigRepository) CreateBuildConfig(
+	bc *models.BuildConfig,
+) (*models.BuildConfig, error) {
+	if !repo.canQuery {
+		return nil, errors.New("cannot write database")
+	}
+
+	repo.buildConfigs = append(repo.buildConfigs, bc)
+	bc.ID = uint(len(repo.buildConfigs))
+
+	return bc, nil
+}
+
+func (repo *BuildConfigRepository) UpdateBuildConfig(bc *models.BuildConfig) (*models.BuildConfig, error) {
+	if !repo.canQuery {
+		return nil, errors.New("cannot write database")
+	}
+
+	// TODO
+	return bc, nil
+}
+
+func (repo *BuildConfigRepository) GetBuildConfig(id uint) (*models.BuildConfig, error) {
+	if !repo.canQuery {
+		return nil, errors.New("cannot write database")
+	}
+
+	// TODO
+	return nil, nil
+}

+ 205 - 0
internal/repository/test/cluster.go

@@ -0,0 +1,205 @@
+package test
+
+import (
+	"errors"
+
+	"github.com/porter-dev/porter/internal/features"
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+	"gorm.io/gorm"
+
+	ints "github.com/porter-dev/porter/internal/models/integrations"
+)
+
+// ClusterRepository implements repository.ClusterRepository
+type ClusterRepository struct {
+	canQuery          bool
+	clusterCandidates []*models.ClusterCandidate
+	clusters          []*models.Cluster
+}
+
+// NewClusterRepository will return errors if canQuery is false
+func NewClusterRepository(canQuery bool) repository.ClusterRepository {
+	return &ClusterRepository{
+		canQuery,
+		[]*models.ClusterCandidate{},
+		[]*models.Cluster{},
+	}
+}
+
+// CreateClusterCandidate creates a new cluster candidate
+func (repo *ClusterRepository) CreateClusterCandidate(
+	cc *models.ClusterCandidate,
+) (*models.ClusterCandidate, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	repo.clusterCandidates = append(repo.clusterCandidates, cc)
+	cc.ID = uint(len(repo.clusterCandidates))
+
+	return cc, nil
+}
+
+// ReadClusterCandidate finds a service account candidate by id
+func (repo *ClusterRepository) ReadClusterCandidate(projectID, id uint) (*models.ClusterCandidate, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	if int(id-1) >= len(repo.clusterCandidates) || repo.clusterCandidates[id-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(id - 1)
+	return repo.clusterCandidates[index], nil
+}
+
+// ListClusterCandidatesByProjectID finds all service account candidates
+// for a given project id
+func (repo *ClusterRepository) ListClusterCandidatesByProjectID(
+	projectID uint,
+) ([]*models.ClusterCandidate, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	res := make([]*models.ClusterCandidate, 0)
+
+	for _, saCandidate := range repo.clusterCandidates {
+		if saCandidate.ProjectID == projectID {
+			res = append(res, saCandidate)
+		}
+	}
+
+	return res, nil
+}
+
+// UpdateClusterCandidateCreatedClusterID updates the CreatedClusterID for
+// a candidate, after the candidate has been resolved.
+func (repo *ClusterRepository) UpdateClusterCandidateCreatedClusterID(
+	id uint,
+	createdClusterID uint,
+) (*models.ClusterCandidate, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	index := int(id - 1)
+	repo.clusterCandidates[index].CreatedClusterID = createdClusterID
+
+	return repo.clusterCandidates[index], nil
+}
+
+// CreateCluster creates a new servicea account
+func (repo *ClusterRepository) CreateCluster(
+	cluster *models.Cluster,
+	launchDarklyClient *features.Client,
+) (*models.Cluster, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	if cluster == nil {
+		return nil, nil
+	}
+
+	repo.clusters = append(repo.clusters, cluster)
+	cluster.ID = uint(len(repo.clusters))
+
+	return cluster, nil
+}
+
+// ReadCluster finds a service account by id
+func (repo *ClusterRepository) ReadCluster(
+	projectID, id uint,
+) (*models.Cluster, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	if int(id-1) >= len(repo.clusters) || repo.clusters[id-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(id - 1)
+	return repo.clusters[index], nil
+}
+
+func (repo *ClusterRepository) ReadClusterByInfraID(
+	projectID, infraID uint,
+) (*models.Cluster, error) {
+	panic("unimplemented")
+}
+
+// ListClustersByProjectID finds all service accounts
+// for a given project id
+func (repo *ClusterRepository) ListClustersByProjectID(
+	projectID uint,
+) ([]*models.Cluster, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	res := make([]*models.Cluster, 0)
+
+	for _, cluster := range repo.clusters {
+		if cluster != nil && cluster.ProjectID == projectID {
+			res = append(res, cluster)
+		}
+	}
+
+	return res, nil
+}
+
+// UpdateCluster modifies an existing Cluster in the database
+func (repo *ClusterRepository) UpdateCluster(
+	cluster *models.Cluster,
+	launchDarklyClient *features.Client,
+) (*models.Cluster, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	if int(cluster.ID-1) >= len(repo.clusters) || repo.clusters[cluster.ID-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(cluster.ID - 1)
+	repo.clusters[index] = cluster
+
+	return cluster, nil
+}
+
+// UpdateClusterTokenCache updates the token cache for a cluster
+func (repo *ClusterRepository) UpdateClusterTokenCache(
+	tokenCache *ints.ClusterTokenCache,
+) (*models.Cluster, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	index := int(tokenCache.ClusterID - 1)
+	repo.clusters[index].TokenCache.Token = tokenCache.Token
+	repo.clusters[index].TokenCache.Expiry = tokenCache.Expiry
+
+	return repo.clusters[index], nil
+}
+
+// DeleteCluster removes a cluster from the array by setting it to nil
+func (repo *ClusterRepository) DeleteCluster(
+	cluster *models.Cluster,
+) error {
+	if !repo.canQuery {
+		return errors.New("Cannot write database")
+	}
+
+	if int(cluster.ID-1) >= len(repo.clusters) || repo.clusters[cluster.ID-1] == nil {
+		return gorm.ErrRecordNotFound
+	}
+
+	index := int(cluster.ID - 1)
+	repo.clusters[index] = nil
+
+	return nil
+}

+ 45 - 0
internal/repository/test/cred_exchange_token.go

@@ -0,0 +1,45 @@
+package test
+
+import (
+	"errors"
+
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+	"gorm.io/gorm"
+)
+
+type CredentialsExchangeTokenRepository struct {
+	canQuery bool
+	ceTokens []*models.CredentialsExchangeToken
+}
+
+func NewCredentialsExchangeTokenRepository(canQuery bool) repository.CredentialsExchangeTokenRepository {
+	return &CredentialsExchangeTokenRepository{canQuery, []*models.CredentialsExchangeToken{}}
+}
+
+func (repo *CredentialsExchangeTokenRepository) CreateCredentialsExchangeToken(
+	a *models.CredentialsExchangeToken,
+) (*models.CredentialsExchangeToken, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	repo.ceTokens = append(repo.ceTokens, a)
+	a.ID = uint(len(repo.ceTokens))
+
+	return a, nil
+}
+
+// ReadPWResetToken gets an auth code object specified by the unique code
+func (repo *CredentialsExchangeTokenRepository) ReadCredentialsExchangeToken(id uint) (*models.CredentialsExchangeToken, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	if int(id-1) >= len(repo.ceTokens) || repo.ceTokens[id-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(id - 1)
+	return repo.ceTokens[index], nil
+}

+ 36 - 0
internal/repository/test/database.go

@@ -0,0 +1,36 @@
+package test
+
+import (
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+)
+
+type DatabaseRepository struct{}
+
+func NewDatabaseRepository() repository.DatabaseRepository {
+	return &DatabaseRepository{}
+}
+
+func (repo *DatabaseRepository) CreateDatabase(database *models.Database) (*models.Database, error) {
+	panic("unimplemented")
+}
+
+func (repo *DatabaseRepository) ReadDatabase(projectID, clusterID, databaseID uint) (*models.Database, error) {
+	panic("unimplemented")
+}
+
+func (repo *DatabaseRepository) ReadDatabaseByInfraID(projectID, infraID uint) (*models.Database, error) {
+	panic("unimplemented")
+}
+
+func (repo *DatabaseRepository) UpdateDatabase(database *models.Database) (*models.Database, error) {
+	panic("unimplemented")
+}
+
+func (repo *DatabaseRepository) DeleteDatabase(projectID, clusterID, databaseID uint) error {
+	panic("unimplemented")
+}
+
+func (repo *DatabaseRepository) ListDatabases(projectID, clusterID uint) ([]*models.Database, error) {
+	panic("unimplemented")
+}

+ 44 - 0
internal/repository/test/datastore.go

@@ -0,0 +1,44 @@
+package test
+
+import (
+	"context"
+	"errors"
+
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+)
+
+// DatastoreRepository is a test repository that implements repository.DatastoreRepository
+type DatastoreRepository struct {
+	canQuery bool
+}
+
+// NewDatastoreRepository returns the test DatastoreRepository
+func NewDatastoreRepository() repository.DatastoreRepository {
+	return &DatastoreRepository{canQuery: false}
+}
+
+// GetByProjectIDAndName retrieves a datastore by project id and name
+func (repo *DatastoreRepository) GetByProjectIDAndName(ctx context.Context, projectID uint, name string) (*models.Datastore, error) {
+	return nil, errors.New("cannot read database")
+}
+
+// Insert inserts a datastore into the database
+func (repo *DatastoreRepository) Insert(ctx context.Context, datastore *models.Datastore) (*models.Datastore, error) {
+	return nil, errors.New("cannot write database")
+}
+
+// ListByProjectID retrieves a list of datastores by project id
+func (repo *DatastoreRepository) ListByProjectID(ctx context.Context, projectID uint) ([]*models.Datastore, error) {
+	return nil, errors.New("cannot read database")
+}
+
+// Delete deletes a datastore by id
+func (repo *DatastoreRepository) Delete(ctx context.Context, datastore *models.Datastore) (*models.Datastore, error) {
+	return nil, errors.New("cannot write database")
+}
+
+// UpdateStatus updates the status of a datastore
+func (repo *DatastoreRepository) UpdateStatus(ctx context.Context, datastore *models.Datastore, status models.DatastoreStatus) (*models.Datastore, error) {
+	return nil, errors.New("cannot write database")
+}

+ 38 - 0
internal/repository/test/deployment_target.go

@@ -0,0 +1,38 @@
+package test
+
+import (
+	"errors"
+
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+)
+
+// DeploymentTargetRepository is a test repository that implements repository.DeploymentTargetRepository
+type DeploymentTargetRepository struct {
+	canQuery bool
+}
+
+// NewDeploymentTargetRepository returns the test DeploymentTargetRepository
+func NewDeploymentTargetRepository() repository.DeploymentTargetRepository {
+	return &DeploymentTargetRepository{canQuery: false}
+}
+
+// DeploymentTargetBySelectorAndSelectorType finds a deployment target for a projectID and clusterID by its selector and selector type
+func (repo *DeploymentTargetRepository) DeploymentTargetBySelectorAndSelectorType(projectID uint, clusterID uint, selector, selectorType string) (*models.DeploymentTarget, error) {
+	return nil, errors.New("cannot read database")
+}
+
+// List returns all deployment targets for a project
+func (repo *DeploymentTargetRepository) List(projectID uint, clusterID uint, preview bool) ([]*models.DeploymentTarget, error) {
+	return nil, errors.New("cannot read database")
+}
+
+// CreateDeploymentTarget creates a new deployment target
+func (repo *DeploymentTargetRepository) CreateDeploymentTarget(deploymentTarget *models.DeploymentTarget) (*models.DeploymentTarget, error) {
+	return nil, errors.New("cannot write database")
+}
+
+// DeploymentTarget finds a deployment target by its id if a uuid is provided or by name
+func (repo *DeploymentTargetRepository) DeploymentTarget(projectID uint, deploymentTargetIdentifier string) (*models.DeploymentTarget, error) {
+	return nil, errors.New("cannot read database")
+}

+ 36 - 0
internal/repository/test/dns_record.go

@@ -0,0 +1,36 @@
+package test
+
+import (
+	"errors"
+
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+)
+
+// DNSRecordRepository implements repository.DNSRecordRepository
+type DNSRecordRepository struct {
+	canQuery   bool
+	dnsRecords []*models.DNSRecord
+}
+
+// NewDNSRecordRepository will return errors if canQuery is false
+func NewDNSRecordRepository(canQuery bool) repository.DNSRecordRepository {
+	return &DNSRecordRepository{
+		canQuery,
+		[]*models.DNSRecord{},
+	}
+}
+
+// CreateDNSRecord creates a new repoistry
+func (repo *DNSRecordRepository) CreateDNSRecord(
+	record *models.DNSRecord,
+) (*models.DNSRecord, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	repo.dnsRecords = append(repo.dnsRecords, record)
+	record.ID = uint(len(repo.dnsRecords))
+
+	return record, nil
+}

+ 86 - 0
internal/repository/test/environment.go

@@ -0,0 +1,86 @@
+package test
+
+import (
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+)
+
+// EnvironmentRepository uses gorm.DB for querying the database
+type EnvironmentRepository struct{}
+
+// NewEnvironmentRepository returns a DefaultEnvironmentRepository which uses
+// gorm.DB for querying the database
+func NewEnvironmentRepository() repository.EnvironmentRepository {
+	return &EnvironmentRepository{}
+}
+
+func (repo *EnvironmentRepository) CreateEnvironment(env *models.Environment) (*models.Environment, error) {
+	panic("unimplemented")
+}
+
+func (repo *EnvironmentRepository) ReadEnvironment(projectID, clusterID, gitInstallationID uint, gitRepoOwner, gitRepoName string) (*models.Environment, error) {
+	panic("unimplemented")
+}
+
+func (repo *EnvironmentRepository) ReadEnvironmentByID(projectID, clusterID, envID uint) (*models.Environment, error) {
+	panic("unimplemented")
+}
+
+func (repo *EnvironmentRepository) ReadEnvironmentByOwnerRepoName(
+	projectID, clusterID uint,
+	gitRepoOwner, gitRepoName string,
+) (*models.Environment, error) {
+	panic("unimplemented")
+}
+
+func (repo *EnvironmentRepository) ReadEnvironmentByWebhookIDOwnerRepoName(webhookID, owner, repoName string) (*models.Environment, error) {
+	panic("unimplemented")
+}
+
+func (repo *EnvironmentRepository) ListEnvironments(projectID, clusterID uint) ([]*models.Environment, error) {
+	panic("unimplemented")
+}
+
+func (repo *EnvironmentRepository) UpdateEnvironment(environment *models.Environment) (*models.Environment, error) {
+	panic("unimplemented")
+}
+
+func (repo *EnvironmentRepository) DeleteEnvironment(env *models.Environment) (*models.Environment, error) {
+	panic("unimplemented")
+}
+
+func (repo *EnvironmentRepository) CreateDeployment(deployment *models.Deployment) (*models.Deployment, error) {
+	panic("unimplemented")
+}
+
+func (repo *EnvironmentRepository) UpdateDeployment(deployment *models.Deployment) (*models.Deployment, error) {
+	panic("unimplemented")
+}
+
+func (repo *EnvironmentRepository) ReadDeployment(environmentID uint, namespace string) (*models.Deployment, error) {
+	panic("unimplemented")
+}
+
+func (repo *EnvironmentRepository) ReadDeploymentByID(projectID, clusterID, id uint) (*models.Deployment, error) {
+	panic("unimplemented")
+}
+
+func (repo *EnvironmentRepository) ReadDeploymentByGitDetails(environmentID uint, owner, repoName string, prNumber uint) (*models.Deployment, error) {
+	panic("unimplemented")
+}
+
+func (repo *EnvironmentRepository) ReadDeploymentForBranch(environmentID uint, owner, name, branch string) (*models.Deployment, error) {
+	panic("unimplemented")
+}
+
+func (repo *EnvironmentRepository) ListDeploymentsByCluster(projectID, clusterID uint, states ...string) ([]*models.Deployment, error) {
+	panic("unimplemented")
+}
+
+func (repo *EnvironmentRepository) ListDeployments(environmentID uint, states ...string) ([]*models.Deployment, error) {
+	panic("unimplemented")
+}
+
+func (repo *EnvironmentRepository) DeleteDeployment(deployment *models.Deployment) (*models.Deployment, error) {
+	panic("unimplemented")
+}

+ 75 - 0
internal/repository/test/event.go

@@ -0,0 +1,75 @@
+package test
+
+import (
+	"github.com/porter-dev/porter/api/types"
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+)
+
+type BuildEventRepository struct{}
+
+func NewBuildEventRepository(canQuery bool) repository.BuildEventRepository {
+	return &BuildEventRepository{}
+}
+
+func (n *BuildEventRepository) CreateEventContainer(am *models.EventContainer) (*models.EventContainer, error) {
+	panic("not implemented") // TODO: Implement
+}
+
+func (n *BuildEventRepository) CreateSubEvent(am *models.SubEvent) (*models.SubEvent, error) {
+	panic("not implemented") // TODO: Implement
+}
+
+func (n *BuildEventRepository) ReadEventsByContainerID(id uint) ([]*models.SubEvent, error) {
+	panic("not implemented") // TODO: Implement
+}
+
+func (n *BuildEventRepository) ReadEventContainer(id uint) (*models.EventContainer, error) {
+	panic("not implemented") // TODO: Implement
+}
+
+func (n *BuildEventRepository) ReadSubEvent(id uint) (*models.SubEvent, error) {
+	panic("not implemented") // TODO: Implement
+}
+
+func (n *BuildEventRepository) AppendEvent(container *models.EventContainer, event *models.SubEvent) error {
+	panic("not implemented") // TODO: Implement
+}
+
+type KubeEventRepository struct{}
+
+func NewKubeEventRepository(canQuery bool) repository.KubeEventRepository {
+	return &KubeEventRepository{}
+}
+
+func (n *KubeEventRepository) CreateEvent(event *models.KubeEvent) (*models.KubeEvent, error) {
+	panic("not implemented") // TODO: Implement
+}
+
+func (n *KubeEventRepository) ReadEvent(id uint, projID uint, clusterID uint) (*models.KubeEvent, error) {
+	panic("not implemented") // TODO: Implement
+}
+
+func (n *KubeEventRepository) ReadEventByGroup(
+	projID uint,
+	clusterID uint,
+	opts *types.GroupOptions,
+) (*models.KubeEvent, error) {
+	panic("not implemented") // TODO: Implement
+}
+
+func (n *KubeEventRepository) ListEventsByProjectID(
+	projectID uint,
+	clusterID uint,
+	opts *types.ListKubeEventRequest,
+) ([]*models.KubeEvent, int64, error) {
+	panic("not implemented") // TODO: Implement
+}
+
+func (n *KubeEventRepository) AppendSubEvent(event *models.KubeEvent, subEvent *models.KubeSubEvent) error {
+	panic("not implemented") // TODO: Implement
+}
+
+func (n *KubeEventRepository) DeleteEvent(id uint) error {
+	panic("not implemented") // TODO: Implement
+}

+ 62 - 0
internal/repository/test/git_action_config.go

@@ -0,0 +1,62 @@
+package test
+
+import (
+	"errors"
+
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+	"gorm.io/gorm"
+)
+
+// GitActionConfigRepository uses gorm.DB for querying the database
+type GitActionConfigRepository struct {
+	canQuery         bool
+	gitActionConfigs []*models.GitActionConfig
+}
+
+func NewGitActionConfigRepository(canQuery bool) repository.GitActionConfigRepository {
+	return &GitActionConfigRepository{
+		canQuery,
+		[]*models.GitActionConfig{},
+	}
+}
+
+// CreateGitActionConfig creates a new git repo
+func (repo *GitActionConfigRepository) CreateGitActionConfig(gac *models.GitActionConfig) (*models.GitActionConfig, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	repo.gitActionConfigs = append(repo.gitActionConfigs, gac)
+	gac.ID = uint(len(repo.gitActionConfigs))
+
+	return gac, nil
+}
+
+// ReadGitActionConfig gets a git repo specified by a unique id
+func (repo *GitActionConfigRepository) ReadGitActionConfig(id uint) (*models.GitActionConfig, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	if int(id-1) >= len(repo.gitActionConfigs) || repo.gitActionConfigs[id-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(id - 1)
+	return repo.gitActionConfigs[index], nil
+}
+
+func (repo *GitActionConfigRepository) UpdateGitActionConfig(gac *models.GitActionConfig) error {
+	if !repo.canQuery {
+		return errors.New("Cannot write database")
+	}
+
+	if int(gac.ID-1) >= len(repo.gitActionConfigs) || repo.gitActionConfigs[gac.ID-1] == nil {
+		return gorm.ErrRecordNotFound
+	}
+
+	index := int(gac.ID - 1)
+	repo.gitActionConfigs[index] = gac
+	return nil
+}

+ 35 - 0
internal/repository/test/github_webhook.go

@@ -0,0 +1,35 @@
+package test
+
+import (
+	"context"
+	"errors"
+
+	"github.com/google/uuid"
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+)
+
+// GithubWebhookRepository is a test repository that implements repository.GithubWebhookRepository
+type GithubWebhookRepository struct {
+	canQuery bool
+}
+
+// NewGithubWebhookRepository returns the test GithubWebhookRepository
+func NewGithubWebhookRepository() repository.GithubWebhookRepository {
+	return &GithubWebhookRepository{canQuery: false}
+}
+
+// Insert inserts a new GithubWebhook into the db
+func (repo *GithubWebhookRepository) Insert(ctx context.Context, webhook *models.GithubWebhook) (*models.GithubWebhook, error) {
+	return nil, errors.New("cannot write database")
+}
+
+// GetByClusterAndAppID finds a GithubWebhook by clusterID and appID
+func (repo *GithubWebhookRepository) GetByClusterAndAppID(ctx context.Context, clusterID, appID uint) (*models.GithubWebhook, error) {
+	return nil, errors.New("cannot read database")
+}
+
+// Get finds a GithubWebhook by id
+func (repo *GithubWebhookRepository) Get(ctx context.Context, id uuid.UUID) (*models.GithubWebhook, error) {
+	return nil, errors.New("cannot read database")
+}

+ 103 - 0
internal/repository/test/gitrepo.go

@@ -0,0 +1,103 @@
+package test
+
+import (
+	"errors"
+
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+
+	"gorm.io/gorm"
+)
+
+// GitRepoRepository implements repository.GitRepoRepository
+type GitRepoRepository struct {
+	canQuery bool
+	gitRepos []*models.GitRepo
+}
+
+// NewGitRepoRepository will return errors if canQuery is false
+func NewGitRepoRepository(canQuery bool) repository.GitRepoRepository {
+	return &GitRepoRepository{
+		canQuery,
+		[]*models.GitRepo{},
+	}
+}
+
+// CreateGitRepo creates a new repo client and appends it to the in-memory list
+func (repo *GitRepoRepository) CreateGitRepo(gr *models.GitRepo) (*models.GitRepo, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	repo.gitRepos = append(repo.gitRepos, gr)
+	gr.ID = uint(len(repo.gitRepos))
+
+	return gr, nil
+}
+
+// ReadGitRepo returns a repo client by id
+func (repo *GitRepoRepository) ReadGitRepo(id uint) (*models.GitRepo, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	if int(id-1) >= len(repo.gitRepos) || repo.gitRepos[id-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(id - 1)
+	return repo.gitRepos[index], nil
+}
+
+// ListGitReposByProjectID returns a list of repo clients that match a project id
+func (repo *GitRepoRepository) ListGitReposByProjectID(projectID uint) ([]*models.GitRepo, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	res := make([]*models.GitRepo, 0)
+
+	for _, gr := range repo.gitRepos {
+		if gr != nil && gr.ProjectID == projectID {
+			res = append(res, gr)
+		}
+	}
+
+	return res, nil
+}
+
+// UpdateGitRepo modifies an existing GitRepo in the database
+func (repo *GitRepoRepository) UpdateGitRepo(
+	gr *models.GitRepo,
+) (*models.GitRepo, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	if int(gr.ID-1) >= len(repo.gitRepos) || repo.gitRepos[gr.ID-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(gr.ID - 1)
+	repo.gitRepos[index] = gr
+
+	return gr, nil
+}
+
+// DeleteGitRepo removes a repoistry from the array by setting it to nil
+func (repo *GitRepoRepository) DeleteGitRepo(
+	gr *models.GitRepo,
+) error {
+	if !repo.canQuery {
+		return errors.New("Cannot write database")
+	}
+
+	if int(gr.ID-1) >= len(repo.gitRepos) || repo.gitRepos[gr.ID-1] == nil {
+		return gorm.ErrRecordNotFound
+	}
+
+	index := int(gr.ID - 1)
+	repo.gitRepos[index] = nil
+
+	return nil
+}

+ 126 - 0
internal/repository/test/helm_repo.go

@@ -0,0 +1,126 @@
+package test
+
+import (
+	"errors"
+
+	"github.com/porter-dev/porter/internal/models"
+	ints "github.com/porter-dev/porter/internal/models/integrations"
+	"github.com/porter-dev/porter/internal/repository"
+	"gorm.io/gorm"
+)
+
+// HelmRepoRepository implements repository.HelmRepoRepository
+type HelmRepoRepository struct {
+	canQuery  bool
+	helmRepos []*models.HelmRepo
+}
+
+// NewHelmRepoRepository will return errors if canQuery is false
+func NewHelmRepoRepository(canQuery bool) repository.HelmRepoRepository {
+	return &HelmRepoRepository{
+		canQuery,
+		[]*models.HelmRepo{},
+	}
+}
+
+// CreateHelmRepo creates a new repoistry
+func (repo *HelmRepoRepository) CreateHelmRepo(
+	hr *models.HelmRepo,
+) (*models.HelmRepo, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	repo.helmRepos = append(repo.helmRepos, hr)
+	hr.ID = uint(len(repo.helmRepos))
+
+	return hr, nil
+}
+
+// ReadHelmRepo finds a repoistry by id
+func (repo *HelmRepoRepository) ReadHelmRepo(
+	projectID uint,
+	id uint,
+) (*models.HelmRepo, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	if int(id-1) >= len(repo.helmRepos) || repo.helmRepos[id-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(id - 1)
+	return repo.helmRepos[index], nil
+}
+
+// ListHelmReposByProjectID finds all repoistries
+// for a given project id
+func (repo *HelmRepoRepository) ListHelmReposByProjectID(
+	projectID uint,
+) ([]*models.HelmRepo, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	res := make([]*models.HelmRepo, 0)
+
+	for _, hr := range repo.helmRepos {
+		if hr != nil && hr.ProjectID == projectID {
+			res = append(res, hr)
+		}
+	}
+
+	return res, nil
+}
+
+// UpdateHelmRepo modifies an existing HelmRepo in the database
+func (repo *HelmRepoRepository) UpdateHelmRepo(
+	hr *models.HelmRepo,
+) (*models.HelmRepo, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	if int(hr.ID-1) >= len(repo.helmRepos) || repo.helmRepos[hr.ID-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(hr.ID - 1)
+	repo.helmRepos[index] = hr
+
+	return hr, nil
+}
+
+// UpdateHelmRepoTokenCache updates the token cache for a repoistry
+func (repo *HelmRepoRepository) UpdateHelmRepoTokenCache(
+	tokenCache *ints.HelmRepoTokenCache,
+) (*models.HelmRepo, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	index := int(tokenCache.HelmRepoID - 1)
+	repo.helmRepos[index].TokenCache.Token = tokenCache.Token
+	repo.helmRepos[index].TokenCache.Expiry = tokenCache.Expiry
+
+	return repo.helmRepos[index], nil
+}
+
+// DeleteHelmRepo removes a repoistry from the array by setting it to nil
+func (repo *HelmRepoRepository) DeleteHelmRepo(
+	hr *models.HelmRepo,
+) error {
+	if !repo.canQuery {
+		return errors.New("Cannot write database")
+	}
+
+	if int(hr.ID-1) >= len(repo.helmRepos) || repo.helmRepos[hr.ID-1] == nil {
+		return gorm.ErrRecordNotFound
+	}
+
+	index := int(hr.ID - 1)
+	repo.helmRepos[index] = nil
+
+	return nil
+}

+ 115 - 0
internal/repository/test/infra.go

@@ -0,0 +1,115 @@
+package test
+
+import (
+	"errors"
+
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+	"gorm.io/gorm"
+)
+
+// InfraRepository implements repository.InfraRepository
+type InfraRepository struct {
+	canQuery bool
+	infras   []*models.Infra
+}
+
+// NewInfraRepository will return errors if canQuery is false
+func NewInfraRepository(canQuery bool) repository.InfraRepository {
+	return &InfraRepository{
+		canQuery,
+		[]*models.Infra{},
+	}
+}
+
+// CreateInfra creates a new aws infra
+func (repo *InfraRepository) CreateInfra(
+	infra *models.Infra,
+) (*models.Infra, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	repo.infras = append(repo.infras, infra)
+	infra.ID = uint(len(repo.infras))
+
+	return infra, nil
+}
+
+// ReadInfra finds a aws infra by id
+func (repo *InfraRepository) ReadInfra(
+	projectID,
+	id uint,
+) (*models.Infra, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	if int(id-1) >= len(repo.infras) || repo.infras[id-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(id - 1)
+	return repo.infras[index], nil
+}
+
+// ListInfrasByProjectID finds all aws infras
+// for a given project id
+func (repo *InfraRepository) ListInfrasByProjectID(
+	projectID uint,
+	apiVersion string,
+) ([]*models.Infra, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	res := make([]*models.Infra, 0)
+
+	for _, infra := range repo.infras {
+		if infra != nil && infra.ProjectID == projectID {
+			res = append(res, infra)
+		}
+	}
+
+	return res, nil
+}
+
+// UpdateInfra modifies an existing Infra in the database
+func (repo *InfraRepository) UpdateInfra(
+	ai *models.Infra,
+) (*models.Infra, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	if int(ai.ID-1) >= len(repo.infras) || repo.infras[ai.ID-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(ai.ID - 1)
+	repo.infras[index] = ai
+
+	return ai, nil
+}
+
+func (repo *InfraRepository) AddOperation(infra *models.Infra, operation *models.Operation) (*models.Operation, error) {
+	panic("unimplemented")
+}
+
+func (repo *InfraRepository) GetLatestOperation(infra *models.Infra) (*models.Operation, error) {
+	panic("unimplemented")
+}
+
+func (repo *InfraRepository) ListOperations(infraID uint) ([]*models.Operation, error) {
+	panic("unimplemented")
+}
+
+func (repo *InfraRepository) ReadOperation(infraID uint, operationUID string) (*models.Operation, error) {
+	panic("unimplemented")
+}
+
+func (repo *InfraRepository) UpdateOperation(
+	operation *models.Operation,
+) (*models.Operation, error) {
+	panic("unimplemented")
+}

+ 124 - 0
internal/repository/test/invite.go

@@ -0,0 +1,124 @@
+package test
+
+import (
+	"errors"
+
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+	"gorm.io/gorm"
+)
+
+// InviteRepository uses gorm.DB for querying the database
+type InviteRepository struct {
+	canQuery bool
+	invites  []*models.Invite
+}
+
+// NewInviteRepository returns a InviteRepository which uses
+// gorm.DB for querying the database
+func NewInviteRepository(canQuery bool) repository.InviteRepository {
+	return &InviteRepository{canQuery, []*models.Invite{}}
+}
+
+// CreateInvite creates a new invite
+func (repo *InviteRepository) CreateInvite(invite *models.Invite) (*models.Invite, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	repo.invites = append(repo.invites, invite)
+	invite.ID = uint(len(repo.invites))
+
+	return invite, nil
+}
+
+// ReadInvite gets an invite specified by a unique id
+func (repo *InviteRepository) ReadInvite(projectID, id uint) (*models.Invite, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	if int(id-1) >= len(repo.invites) || repo.invites[id-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(id - 1)
+	return repo.invites[index], nil
+}
+
+// ReadInviteByToken gets an invite specified by a unique token
+func (repo *InviteRepository) ReadInviteByToken(token string) (*models.Invite, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	var res *models.Invite
+
+	for _, invite := range repo.invites {
+		if token == invite.Token {
+			res = invite
+		}
+	}
+
+	if res == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	return res, nil
+}
+
+// ListInvitesByProjectID finds all invites
+// for a given project id
+func (repo *InviteRepository) ListInvitesByProjectID(
+	projectID uint,
+) ([]*models.Invite, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	res := make([]*models.Invite, 0)
+
+	for _, invite := range repo.invites {
+		if invite != nil && invite.ProjectID == projectID {
+			res = append(res, invite)
+		}
+	}
+
+	return res, nil
+}
+
+// UpdateInvite updates an invitation in the DB
+func (repo *InviteRepository) UpdateInvite(
+	invite *models.Invite,
+) (*models.Invite, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	if int(invite.ID-1) >= len(repo.invites) || repo.invites[invite.ID-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(invite.ID - 1)
+	repo.invites[index] = invite
+
+	return invite, nil
+}
+
+// DeleteInvite removes a registry from the db
+func (repo *InviteRepository) DeleteInvite(
+	invite *models.Invite,
+) error {
+	if !repo.canQuery {
+		return errors.New("Cannot write database")
+	}
+
+	if int(invite.ID-1) >= len(repo.invites) || repo.invites[invite.ID-1] == nil {
+		return gorm.ErrRecordNotFound
+	}
+
+	index := int(invite.ID - 1)
+	repo.invites[index] = nil
+
+	return nil
+}

+ 32 - 0
internal/repository/test/monitor.go

@@ -0,0 +1,32 @@
+package test
+
+import (
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+)
+
+type MonitorTestResultRepository struct{}
+
+func NewMonitorTestResultRepository(canQuery bool) repository.MonitorTestResultRepository {
+	return &MonitorTestResultRepository{}
+}
+
+func (n *MonitorTestResultRepository) CreateMonitorTestResult(monitor *models.MonitorTestResult) (*models.MonitorTestResult, error) {
+	panic("not implemented") // TODO: Implement
+}
+
+func (n *MonitorTestResultRepository) ReadMonitorTestResult(projectID, clusterID uint, operationID string) (*models.MonitorTestResult, error) {
+	panic("not implemented") // TODO: Implement
+}
+
+func (n *MonitorTestResultRepository) UpdateMonitorTestResult(monitor *models.MonitorTestResult) (*models.MonitorTestResult, error) {
+	panic("not implemented") // TODO: Implement
+}
+
+func (n *MonitorTestResultRepository) ArchiveMonitorTestResults(projectID, clusterID uint, recommenderID string) error {
+	panic("not implemented") // TODO: Implement
+}
+
+func (n *MonitorTestResultRepository) DeleteOldMonitorTestResults(projectID, clusterID uint, recommenderID string) error {
+	panic("not implemented") // TODO: Implement
+}

+ 42 - 0
internal/repository/test/notification.go

@@ -0,0 +1,42 @@
+package test
+
+import (
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+)
+
+type NotificationConfigRepository struct{}
+
+func NewNotificationConfigRepository(canQuery bool) repository.NotificationConfigRepository {
+	return &NotificationConfigRepository{}
+}
+
+func (n *NotificationConfigRepository) CreateNotificationConfig(am *models.NotificationConfig) (*models.NotificationConfig, error) {
+	panic("not implemented") // TODO: Implement
+}
+
+func (n *NotificationConfigRepository) ReadNotificationConfig(id uint) (*models.NotificationConfig, error) {
+	panic("not implemented") // TODO: Implement
+}
+
+func (n *NotificationConfigRepository) UpdateNotificationConfig(am *models.NotificationConfig) (*models.NotificationConfig, error) {
+	panic("not implemented") // TODO: Implement
+}
+
+type JobNotificationConfigRepository struct{}
+
+func NewJobNotificationConfigRepository(canQuery bool) repository.JobNotificationConfigRepository {
+	return &JobNotificationConfigRepository{}
+}
+
+func (n *JobNotificationConfigRepository) CreateNotificationConfig(am *models.JobNotificationConfig) (*models.JobNotificationConfig, error) {
+	panic("not implemented") // TODO: Implement
+}
+
+func (n *JobNotificationConfigRepository) ReadNotificationConfig(projID, clusterID uint, name, namespace string) (*models.JobNotificationConfig, error) {
+	panic("not implemented") // TODO: Implement
+}
+
+func (n *JobNotificationConfigRepository) UpdateNotificationConfig(am *models.JobNotificationConfig) (*models.JobNotificationConfig, error) {
+	panic("not implemented") // TODO: Implement
+}

+ 75 - 0
internal/repository/test/onboarding.go

@@ -0,0 +1,75 @@
+package test
+
+import (
+	"errors"
+
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+	"gorm.io/gorm"
+)
+
+// ProjectOnboardingRepository implements repository.ProjectOnboardingRepository
+type ProjectOnboardingRepository struct {
+	canQuery    bool
+	onboardings []*models.Onboarding
+}
+
+// NewProjectOnboardingRepository will return errors if canQuery is false
+func NewProjectOnboardingRepository(canQuery bool) repository.ProjectOnboardingRepository {
+	return &ProjectOnboardingRepository{
+		canQuery,
+		[]*models.Onboarding{},
+	}
+}
+
+// CreateProjectOnboarding creates a new project onboarding limit
+func (repo *ProjectOnboardingRepository) CreateProjectOnboarding(
+	onboarding *models.Onboarding,
+) (*models.Onboarding, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	if onboarding == nil {
+		return nil, nil
+	}
+
+	repo.onboardings = append(repo.onboardings, onboarding)
+
+	return onboarding, nil
+}
+
+// ReadProjectOnboarding reads a project onboarding by project id
+func (repo *ProjectOnboardingRepository) ReadProjectOnboarding(
+	projID uint,
+) (*models.Onboarding, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	for _, pu := range repo.onboardings {
+		if pu != nil && pu.ProjectID == projID {
+			return pu, nil
+		}
+	}
+
+	return nil, gorm.ErrRecordNotFound
+}
+
+// UpdateProjectOnboarding modifies an existing ProjectOnboarding in the database
+func (repo *ProjectOnboardingRepository) UpdateProjectOnboarding(
+	onboarding *models.Onboarding,
+) (*models.Onboarding, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	if int(onboarding.ID-1) >= len(repo.onboardings) || repo.onboardings[onboarding.ID-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(onboarding.ID - 1)
+	repo.onboardings[index] = onboarding
+
+	return onboarding, nil
+}

+ 40 - 0
internal/repository/test/policy.go

@@ -0,0 +1,40 @@
+package test
+
+import (
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+)
+
+type PolicyRepository struct {
+	canQuery bool
+}
+
+// NewPolicyRepository returns a PolicyRepository which uses
+// gorm.DB for querying the database
+func NewPolicyRepository(canQuery bool) repository.PolicyRepository {
+	return &PolicyRepository{canQuery}
+}
+
+func (repo *PolicyRepository) CreatePolicy(a *models.Policy) (*models.Policy, error) {
+	panic("unimplemented")
+}
+
+func (repo *PolicyRepository) ListPoliciesByProjectID(projectID uint) ([]*models.Policy, error) {
+	panic("unimplemented")
+}
+
+func (repo *PolicyRepository) ReadPolicy(projectID uint, uid string) (*models.Policy, error) {
+	panic("unimplemented")
+}
+
+func (repo *PolicyRepository) UpdatePolicy(
+	policy *models.Policy,
+) (*models.Policy, error) {
+	panic("unimplemented")
+}
+
+func (repo *PolicyRepository) DeletePolicy(
+	policy *models.Policy,
+) (*models.Policy, error) {
+	panic("unimplemented")
+}

+ 50 - 0
internal/repository/test/porter_app.go

@@ -0,0 +1,50 @@
+package test
+
+import (
+	"context"
+	"errors"
+	"strings"
+
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+)
+
+type PorterAppRepository struct {
+	canQuery       bool
+	failingMethods string
+}
+
+func NewPorterAppRepository(canQuery bool, failingMethods ...string) repository.PorterAppRepository {
+	return &PorterAppRepository{canQuery, strings.Join(failingMethods, ",")}
+}
+
+func (repo *PorterAppRepository) ReadPorterAppByName(clusterID uint, name string) (*models.PorterApp, error) {
+	return nil, errors.New("cannot write database")
+}
+
+// ReadPorterAppsByProjectIDAndName is a test method that is not implemented
+func (repo *PorterAppRepository) ReadPorterAppsByProjectIDAndName(projectID uint, name string) ([]*models.PorterApp, error) {
+	return nil, errors.New("cannot write database")
+}
+
+func (repo *PorterAppRepository) CreatePorterApp(app *models.PorterApp) (*models.PorterApp, error) {
+	return nil, errors.New("cannot write database")
+}
+
+func (repo *PorterAppRepository) UpdatePorterApp(app *models.PorterApp) (*models.PorterApp, error) {
+	return nil, errors.New("cannot write database")
+}
+
+// ListPorterAppByClusterID is a test method that is not implemented
+func (repo *PorterAppRepository) ListPorterAppByClusterID(clusterID uint) ([]*models.PorterApp, error) {
+	return nil, errors.New("cannot write database")
+}
+
+func (repo *PorterAppRepository) DeletePorterApp(app *models.PorterApp) (*models.PorterApp, error) {
+	return nil, errors.New("cannot write database")
+}
+
+// ReadPorterAppByID is a test method that is not implemented
+func (repo *PorterAppRepository) ReadPorterAppByID(ctx context.Context, id uint) (*models.PorterApp, error) {
+	return nil, errors.New("cannot read database")
+}

+ 64 - 0
internal/repository/test/porter_app_event.go

@@ -0,0 +1,64 @@
+package test
+
+import (
+	"context"
+	"errors"
+
+	"github.com/google/uuid"
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+	"github.com/porter-dev/porter/internal/repository/gorm/helpers"
+)
+
+type PorterAppEventRepository struct {
+	canQuery bool
+}
+
+func NewPorterAppEventRepository(canQuery bool, failingMethods ...string) repository.PorterAppEventRepository {
+	return &PorterAppEventRepository{canQuery: false}
+}
+
+func (repo *PorterAppEventRepository) ListEventsByPorterAppID(ctx context.Context, porterAppID uint, opts ...helpers.QueryOption) ([]*models.PorterAppEvent, helpers.PaginatedResult, error) {
+	return nil, helpers.PaginatedResult{}, errors.New("cannot write database")
+}
+
+// ListEventsByPorterAppIDAndDeploymentTargetID is a test method
+func (repo *PorterAppEventRepository) ListEventsByPorterAppIDAndDeploymentTargetID(ctx context.Context, porterAppID uint, deploymentTargetID uuid.UUID, opts ...helpers.QueryOption) ([]*models.PorterAppEvent, helpers.PaginatedResult, error) {
+	return nil, helpers.PaginatedResult{}, errors.New("cannot write database")
+}
+
+// ListBuildDeployEventsByPorterAppIDAndDeploymentTargetID is a test method
+func (repo *PorterAppEventRepository) ListBuildDeployEventsByPorterAppIDAndDeploymentTargetID(ctx context.Context, porterAppID uint, deploymentTargetID uuid.UUID, opts ...helpers.QueryOption) ([]*models.PorterAppEvent, helpers.PaginatedResult, error) {
+	return nil, helpers.PaginatedResult{}, errors.New("cannot write database")
+}
+
+func (repo *PorterAppEventRepository) CreateEvent(ctx context.Context, appEvent *models.PorterAppEvent) error {
+	return errors.New("cannot write database")
+}
+
+func (repo *PorterAppEventRepository) UpdateEvent(ctx context.Context, appEvent *models.PorterAppEvent) error {
+	return errors.New("cannot update database")
+}
+
+func (repo *PorterAppEventRepository) ReadEvent(ctx context.Context, id uuid.UUID) (models.PorterAppEvent, error) {
+	return models.PorterAppEvent{}, errors.New("cannot read database")
+}
+
+func (repo *PorterAppEventRepository) ReadDeployEventByRevision(ctx context.Context, porterAppID uint, revision float64) (models.PorterAppEvent, error) {
+	return models.PorterAppEvent{}, errors.New("cannot read database")
+}
+
+// ReadDeployEventByAppRevisionID is a test method
+func (repo *PorterAppEventRepository) ReadDeployEventByAppRevisionID(ctx context.Context, porterAppID uint, appRevisionID string) (models.PorterAppEvent, error) {
+	return models.PorterAppEvent{}, errors.New("cannot read database")
+}
+
+// ReadNotificationsByAppRevisionID is a test method
+func (repo *PorterAppEventRepository) ReadNotificationsByAppRevisionID(ctx context.Context, porterAppInstanceID uuid.UUID, appRevisionID string) ([]*models.PorterAppEvent, error) {
+	return nil, errors.New("cannot read database")
+}
+
+// NotificationByID returns a notification by the notification id
+func (repo *PorterAppEventRepository) NotificationByID(ctx context.Context, notificationID string) (*models.PorterAppEvent, error) {
+	return nil, errors.New("cannot read database")
+}

+ 229 - 0
internal/repository/test/project.go

@@ -0,0 +1,229 @@
+package test
+
+import (
+	"errors"
+	"strings"
+
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+	"gorm.io/gorm"
+)
+
+const (
+	CreateProjectMethod        string = "create_project_0"
+	CreateProjectRoleMethod    string = "create_project_role_0"
+	ReadProjectMethod          string = "read_project_0"
+	ListProjectsByUserIDMethod string = "list_projects_by_user_id_0"
+)
+
+// ProjectRepository will return errors on queries if canQuery is false
+// and only stores a small set of projects in-memory that are indexed by their
+// array index + 1
+type ProjectRepository struct {
+	canQuery       bool
+	failingMethods string
+	projects       []*models.Project
+}
+
+// NewProjectRepository will return errors if canQuery is false
+func NewProjectRepository(canQuery bool, failingMethods ...string) repository.ProjectRepository {
+	return &ProjectRepository{canQuery, strings.Join(failingMethods, ","), []*models.Project{}}
+}
+
+// CreateProject appends a new project to the in-memory projects array
+func (repo *ProjectRepository) CreateProject(project *models.Project) (*models.Project, error) {
+	if !repo.canQuery || strings.Contains(repo.failingMethods, CreateProjectMethod) {
+		return nil, errors.New("Cannot write database")
+	}
+
+	repo.projects = append(repo.projects, project)
+	project.ID = uint(len(repo.projects))
+
+	return project, nil
+}
+
+// CreateProjectRole appends a role to the existing array of roles
+func (repo *ProjectRepository) CreateProjectRole(project *models.Project, role *models.Role) (*models.Role, error) {
+	if !repo.canQuery || strings.Contains(repo.failingMethods, CreateProjectRoleMethod) {
+		return nil, errors.New("Cannot write database")
+	}
+
+	if int(project.ID-1) >= len(repo.projects) || repo.projects[project.ID-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(project.ID - 1)
+	oldProject := *repo.projects[index]
+	repo.projects[index] = project
+	project.Roles = append(oldProject.Roles, *role)
+
+	return role, nil
+}
+
+func (repo *ProjectRepository) UpdateProject(project *models.Project) (*models.Project, error) {
+	panic("unimplemented")
+}
+
+// CreateProjectRole appends a role to the existing array of roles
+func (repo *ProjectRepository) UpdateProjectRole(projID uint, role *models.Role) (*models.Role, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	var foundProject *models.Project
+
+	// find all roles matching
+	for _, project := range repo.projects {
+		if project.ID == projID {
+			foundProject = project
+		}
+	}
+
+	if foundProject == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	var index int
+
+	for i, _role := range foundProject.Roles {
+		if _role.UserID == role.UserID {
+			index = i
+		}
+	}
+
+	if index == 0 {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	foundProject.Roles[index] = *role
+	return role, nil
+}
+
+// ReadProject gets a projects specified by a unique id
+func (repo *ProjectRepository) ReadProjectRole(userID, projID uint) (*models.Role, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	if int(projID-1) >= len(repo.projects) || repo.projects[projID-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	// find role in project roles
+	index := int(projID - 1)
+
+	for _, role := range repo.projects[index].Roles {
+		if role.UserID == userID {
+			return &role, nil
+		}
+	}
+
+	return nil, gorm.ErrRecordNotFound
+}
+
+// ReadProject gets a projects specified by a unique id
+func (repo *ProjectRepository) ReadProject(id uint) (*models.Project, error) {
+	if !repo.canQuery || strings.Contains(repo.failingMethods, ReadProjectMethod) {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	if int(id-1) >= len(repo.projects) || repo.projects[id-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(id - 1)
+	return repo.projects[index], nil
+}
+
+// ListProjectsByUserID lists projects where a user has an associated role
+func (repo *ProjectRepository) ListProjectsByUserID(userID uint) ([]*models.Project, error) {
+	if !repo.canQuery || strings.Contains(repo.failingMethods, ListProjectsByUserIDMethod) {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	resp := make([]*models.Project, 0)
+
+	// find all roles matching
+	for _, project := range repo.projects {
+		for _, role := range project.Roles {
+			if role.UserID == userID {
+				resp = append(resp, project)
+			}
+		}
+	}
+
+	return resp, nil
+}
+
+// ListProjectRoles returns a list of roles for the project
+func (repo *ProjectRepository) ListProjectRoles(projID uint) ([]models.Role, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	if int(projID-1) >= len(repo.projects) || repo.projects[projID-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(projID - 1)
+	repo.projects[index] = nil
+
+	return repo.projects[index].Roles, nil
+}
+
+// DeleteProject removes a project
+func (repo *ProjectRepository) DeleteProject(project *models.Project) (*models.Project, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	if int(project.ID-1) >= len(repo.projects) || repo.projects[project.ID-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(project.ID - 1)
+	repo.projects[index] = nil
+
+	return project, nil
+}
+
+func (repo *ProjectRepository) DeleteProjectRole(projID, userID uint) (*models.Role, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	var foundProject *models.Project
+
+	// find all roles matching
+	for _, project := range repo.projects {
+		if project.ID == projID {
+			foundProject = project
+		}
+	}
+
+	if foundProject == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	var index int
+
+	for i, _role := range foundProject.Roles {
+		if _role.UserID == userID {
+			index = i
+		}
+	}
+
+	if index == 0 {
+		return nil, gorm.ErrRecordNotFound
+	}
+	res := foundProject.Roles[index]
+
+	foundProject.Roles = append(foundProject.Roles[:index], foundProject.Roles[index+1:]...)
+
+	return &res, nil
+}
+
+// DeleteRolesForProject deletes all roles for a project
+func (repo *ProjectRepository) DeleteRolesForProject(projID uint) error {
+	return errors.New("unimplemented")
+}

+ 65 - 0
internal/repository/test/pw_reset_token.go

@@ -0,0 +1,65 @@
+package test
+
+import (
+	"errors"
+
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+	"gorm.io/gorm"
+)
+
+// PWResetTokenRepository uses gorm.DB for querying the database
+type PWResetTokenRepository struct {
+	canQuery      bool
+	pwResetTokens []*models.PWResetToken
+}
+
+// NewPWResetTokenRepository returns a PWResetTokenRepository which uses
+// gorm.DB for querying the database
+func NewPWResetTokenRepository(canQuery bool) repository.PWResetTokenRepository {
+	return &PWResetTokenRepository{canQuery, []*models.PWResetToken{}}
+}
+
+// CreatePWResetToken creates a new invite
+func (repo *PWResetTokenRepository) CreatePWResetToken(a *models.PWResetToken) (*models.PWResetToken, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	repo.pwResetTokens = append(repo.pwResetTokens, a)
+	a.ID = uint(len(repo.pwResetTokens))
+
+	return a, nil
+}
+
+// ReadPWResetToken gets an auth code object specified by the unique code
+func (repo *PWResetTokenRepository) ReadPWResetToken(id uint) (*models.PWResetToken, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	if int(id-1) >= len(repo.pwResetTokens) || repo.pwResetTokens[id-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(id - 1)
+	return repo.pwResetTokens[index], nil
+}
+
+// UpdatePWResetToken modifies an existing PWResetToken in the database
+func (repo *PWResetTokenRepository) UpdatePWResetToken(
+	pwToken *models.PWResetToken,
+) (*models.PWResetToken, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	if int(pwToken.ID-1) >= len(repo.pwResetTokens) || repo.pwResetTokens[pwToken.ID-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(pwToken.ID - 1)
+	repo.pwResetTokens[index] = pwToken
+
+	return pwToken, nil
+}

+ 131 - 0
internal/repository/test/registry.go

@@ -0,0 +1,131 @@
+package test
+
+import (
+	"errors"
+
+	"github.com/porter-dev/porter/internal/models"
+	ints "github.com/porter-dev/porter/internal/models/integrations"
+	"github.com/porter-dev/porter/internal/repository"
+	"gorm.io/gorm"
+)
+
+// RegistryRepository implements repository.RegistryRepository
+type RegistryRepository struct {
+	canQuery   bool
+	registries []*models.Registry
+}
+
+// NewRegistryRepository will return errors if canQuery is false
+func NewRegistryRepository(canQuery bool) repository.RegistryRepository {
+	return &RegistryRepository{
+		canQuery,
+		[]*models.Registry{},
+	}
+}
+
+// CreateRegistry creates a new registry
+func (repo *RegistryRepository) CreateRegistry(
+	reg *models.Registry,
+) (*models.Registry, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	repo.registries = append(repo.registries, reg)
+	reg.ID = uint(len(repo.registries))
+
+	return reg, nil
+}
+
+// ReadRegistry finds a registry by id
+func (repo *RegistryRepository) ReadRegistry(
+	projectID, regID uint,
+) (*models.Registry, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	if int(regID-1) >= len(repo.registries) || repo.registries[regID-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(regID - 1)
+	return repo.registries[index], nil
+}
+
+func (repo *RegistryRepository) ReadRegistryByInfraID(
+	projectID, infraID uint,
+) (*models.Registry, error) {
+	panic("unimplemented")
+}
+
+// ListRegistriesByProjectID finds all registries
+// for a given project id
+func (repo *RegistryRepository) ListRegistriesByProjectID(
+	projectID uint,
+) ([]*models.Registry, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	res := make([]*models.Registry, 0)
+
+	for _, reg := range repo.registries {
+		if reg != nil && reg.ProjectID == projectID {
+			res = append(res, reg)
+		}
+	}
+
+	return res, nil
+}
+
+// UpdateRegistry modifies an existing Registry in the database
+func (repo *RegistryRepository) UpdateRegistry(
+	reg *models.Registry,
+) (*models.Registry, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	if int(reg.ID-1) >= len(repo.registries) || repo.registries[reg.ID-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(reg.ID - 1)
+	repo.registries[index] = reg
+
+	return reg, nil
+}
+
+// UpdateRegistryTokenCache updates the token cache for a registry
+func (repo *RegistryRepository) UpdateRegistryTokenCache(
+	tokenCache *ints.RegTokenCache,
+) (*models.Registry, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	index := int(tokenCache.RegistryID - 1)
+	repo.registries[index].TokenCache.Token = tokenCache.Token
+	repo.registries[index].TokenCache.Expiry = tokenCache.Expiry
+
+	return repo.registries[index], nil
+}
+
+// DeleteRegistry removes a registry from the array by setting it to nil
+func (repo *RegistryRepository) DeleteRegistry(
+	reg *models.Registry,
+) error {
+	if !repo.canQuery {
+		return errors.New("Cannot write database")
+	}
+
+	if int(reg.ID-1) >= len(repo.registries) || repo.registries[reg.ID-1] == nil {
+		return gorm.ErrRecordNotFound
+	}
+
+	index := int(reg.ID - 1)
+	repo.registries[index] = nil
+
+	return nil
+}

+ 131 - 0
internal/repository/test/release.go

@@ -0,0 +1,131 @@
+package test
+
+import (
+	"errors"
+
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+	"gorm.io/gorm"
+)
+
+// ReleaseRepository implements repository.ReleaseRepository
+type ReleaseRepository struct {
+	canQuery bool
+	releases []*models.Release
+}
+
+// NewReleaseRepository will return errors if canQuery is false
+func NewReleaseRepository(canQuery bool) repository.ReleaseRepository {
+	return &ReleaseRepository{
+		canQuery,
+		[]*models.Release{},
+	}
+}
+
+// CreateRelease creates a new release
+func (repo *ReleaseRepository) CreateRelease(
+	release *models.Release,
+) (*models.Release, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	if release == nil {
+		return nil, nil
+	}
+
+	repo.releases = append(repo.releases, release)
+	release.ID = uint(len(repo.releases))
+
+	return release, nil
+}
+
+// ReadRelease finds a release by id
+func (repo *ReleaseRepository) ReadReleaseByWebhookToken(
+	token string,
+) (*models.Release, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	for _, release := range repo.releases {
+		if release != nil && release.WebhookToken == token {
+			return release, nil
+		}
+	}
+
+	return nil, gorm.ErrRecordNotFound
+}
+
+// ReadRelease finds a release by id
+func (repo *ReleaseRepository) ReadRelease(
+	clusterID uint, name, namespace string,
+) (*models.Release, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	for _, release := range repo.releases {
+		if release != nil && release.ClusterID == clusterID && release.Name == name && release.Namespace == namespace {
+			return release, nil
+		}
+	}
+
+	return nil, gorm.ErrRecordNotFound
+}
+
+// ListReleasesByProjectID finds all releases for a given project id
+func (repo *ReleaseRepository) ListReleasesByImageRepoURI(
+	clusterID uint, imageRepoURI string,
+) ([]*models.Release, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	res := make([]*models.Release, 0)
+
+	for _, release := range repo.releases {
+		if release != nil && release.ClusterID == clusterID && release.ImageRepoURI == imageRepoURI {
+			res = append(res, release)
+		}
+	}
+
+	return res, nil
+}
+
+// UpdateRelease modifies an existing Release in the database
+func (repo *ReleaseRepository) UpdateRelease(
+	release *models.Release,
+) (*models.Release, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	if int(release.ID-1) >= len(repo.releases) || repo.releases[release.ID-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(release.ID - 1)
+	repo.releases[index] = release
+
+	return release, nil
+}
+
+// DeleteRelease removes a release from the array by setting it to nil
+func (repo *ReleaseRepository) DeleteRelease(
+	release *models.Release,
+) (*models.Release, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	if int(release.ID-1) >= len(repo.releases) || repo.releases[release.ID-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(release.ID - 1)
+	copy := repo.releases[index]
+	repo.releases[index] = nil
+
+	return copy, nil
+}

+ 333 - 0
internal/repository/test/repository.go

@@ -0,0 +1,333 @@
+package test
+
+import (
+	"github.com/porter-dev/porter/internal/repository"
+)
+
+type TestRepository struct {
+	user                      repository.UserRepository
+	session                   repository.SessionRepository
+	project                   repository.ProjectRepository
+	cluster                   repository.ClusterRepository
+	helmRepo                  repository.HelmRepoRepository
+	registry                  repository.RegistryRepository
+	gitRepo                   repository.GitRepoRepository
+	gitActionConfig           repository.GitActionConfigRepository
+	invite                    repository.InviteRepository
+	release                   repository.ReleaseRepository
+	environment               repository.EnvironmentRepository
+	authCode                  repository.AuthCodeRepository
+	dnsRecord                 repository.DNSRecordRepository
+	pwResetToken              repository.PWResetTokenRepository
+	infra                     repository.InfraRepository
+	kubeIntegration           repository.KubeIntegrationRepository
+	basicIntegration          repository.BasicIntegrationRepository
+	oidcIntegration           repository.OIDCIntegrationRepository
+	oauthIntegration          repository.OAuthIntegrationRepository
+	gcpIntegration            repository.GCPIntegrationRepository
+	awsIntegration            repository.AWSIntegrationRepository
+	azIntegration             repository.AzureIntegrationRepository
+	githubAppInstallation     repository.GithubAppInstallationRepository
+	githubAppOAuthIntegration repository.GithubAppOAuthIntegrationRepository
+	gitlabIntegration         repository.GitlabIntegrationRepository
+	gitlabAppOAuthIntegration repository.GitlabAppOAuthIntegrationRepository
+	slackIntegration          repository.SlackIntegrationRepository
+	notificationConfig        repository.NotificationConfigRepository
+	jobNotificationConfig     repository.JobNotificationConfigRepository
+	buildEvent                repository.BuildEventRepository
+	kubeEvent                 repository.KubeEventRepository
+	projectUsage              repository.ProjectUsageRepository
+	onboarding                repository.ProjectOnboardingRepository
+	ceToken                   repository.CredentialsExchangeTokenRepository
+	buildConfig               repository.BuildConfigRepository
+	database                  repository.DatabaseRepository
+	allowlist                 repository.AllowlistRepository
+	apiToken                  repository.APITokenRepository
+	policy                    repository.PolicyRepository
+	tag                       repository.TagRepository
+	stack                     repository.StackRepository
+	monitor                   repository.MonitorTestResultRepository
+	apiContractRevision       repository.APIContractRevisioner
+	awsAssumeRoleChainer      repository.AWSAssumeRoleChainer
+	porterApp                 repository.PorterAppRepository
+	porterAppEvent            repository.PorterAppEventRepository
+	deploymentTarget          repository.DeploymentTargetRepository
+	appRevision               repository.AppRevisionRepository
+	appTemplate               repository.AppTemplateRepository
+	githubWebhook             repository.GithubWebhookRepository
+	datastore                 repository.DatastoreRepository
+	appInstance               repository.AppInstanceRepository
+}
+
+func (t *TestRepository) User() repository.UserRepository {
+	return t.user
+}
+
+func (t *TestRepository) Session() repository.SessionRepository {
+	return t.session
+}
+
+func (t *TestRepository) Project() repository.ProjectRepository {
+	return t.project
+}
+
+func (t *TestRepository) Cluster() repository.ClusterRepository {
+	return t.cluster
+}
+
+func (t *TestRepository) HelmRepo() repository.HelmRepoRepository {
+	return t.helmRepo
+}
+
+func (t *TestRepository) Registry() repository.RegistryRepository {
+	return t.registry
+}
+
+func (t *TestRepository) GitRepo() repository.GitRepoRepository {
+	return t.gitRepo
+}
+
+func (t *TestRepository) GitActionConfig() repository.GitActionConfigRepository {
+	return t.gitActionConfig
+}
+
+func (t *TestRepository) Invite() repository.InviteRepository {
+	return t.invite
+}
+
+func (t *TestRepository) Release() repository.ReleaseRepository {
+	return t.release
+}
+
+func (t *TestRepository) Environment() repository.EnvironmentRepository {
+	return t.environment
+}
+
+func (t *TestRepository) AuthCode() repository.AuthCodeRepository {
+	return t.authCode
+}
+
+func (t *TestRepository) DNSRecord() repository.DNSRecordRepository {
+	return t.dnsRecord
+}
+
+func (t *TestRepository) PWResetToken() repository.PWResetTokenRepository {
+	return t.pwResetToken
+}
+
+func (t *TestRepository) Infra() repository.InfraRepository {
+	return t.infra
+}
+
+func (t *TestRepository) KubeIntegration() repository.KubeIntegrationRepository {
+	return t.kubeIntegration
+}
+
+func (t *TestRepository) BasicIntegration() repository.BasicIntegrationRepository {
+	return t.basicIntegration
+}
+
+func (t *TestRepository) OIDCIntegration() repository.OIDCIntegrationRepository {
+	return t.oidcIntegration
+}
+
+func (t *TestRepository) OAuthIntegration() repository.OAuthIntegrationRepository {
+	return t.oauthIntegration
+}
+
+func (t *TestRepository) GCPIntegration() repository.GCPIntegrationRepository {
+	return t.gcpIntegration
+}
+
+func (t *TestRepository) AWSIntegration() repository.AWSIntegrationRepository {
+	return t.awsIntegration
+}
+
+func (t *TestRepository) AzureIntegration() repository.AzureIntegrationRepository {
+	return t.azIntegration
+}
+
+func (t *TestRepository) GithubAppInstallation() repository.GithubAppInstallationRepository {
+	return t.githubAppInstallation
+}
+
+func (t *TestRepository) GithubAppOAuthIntegration() repository.GithubAppOAuthIntegrationRepository {
+	return t.githubAppOAuthIntegration
+}
+
+func (t *TestRepository) GitlabIntegration() repository.GitlabIntegrationRepository {
+	return t.gitlabIntegration
+}
+
+func (t *TestRepository) GitlabAppOAuthIntegration() repository.GitlabAppOAuthIntegrationRepository {
+	return t.gitlabAppOAuthIntegration
+}
+
+func (t *TestRepository) SlackIntegration() repository.SlackIntegrationRepository {
+	return t.slackIntegration
+}
+
+func (t *TestRepository) NotificationConfig() repository.NotificationConfigRepository {
+	return t.notificationConfig
+}
+
+func (t *TestRepository) JobNotificationConfig() repository.JobNotificationConfigRepository {
+	return t.jobNotificationConfig
+}
+
+func (t *TestRepository) BuildEvent() repository.BuildEventRepository {
+	return t.buildEvent
+}
+
+func (t *TestRepository) KubeEvent() repository.KubeEventRepository {
+	return t.kubeEvent
+}
+
+func (t *TestRepository) ProjectUsage() repository.ProjectUsageRepository {
+	return t.projectUsage
+}
+
+func (t *TestRepository) Onboarding() repository.ProjectOnboardingRepository {
+	return t.onboarding
+}
+
+func (t *TestRepository) CredentialsExchangeToken() repository.CredentialsExchangeTokenRepository {
+	return t.ceToken
+}
+
+func (t *TestRepository) BuildConfig() repository.BuildConfigRepository {
+	return t.buildConfig
+}
+
+func (t *TestRepository) Database() repository.DatabaseRepository {
+	return t.database
+}
+
+func (t *TestRepository) Allowlist() repository.AllowlistRepository {
+	return t.allowlist
+}
+
+func (t *TestRepository) APIToken() repository.APITokenRepository {
+	return t.apiToken
+}
+
+func (t *TestRepository) Policy() repository.PolicyRepository {
+	return t.policy
+}
+
+func (t *TestRepository) Tag() repository.TagRepository {
+	return t.tag
+}
+
+func (t *TestRepository) Stack() repository.StackRepository {
+	return t.stack
+}
+
+func (t *TestRepository) MonitorTestResult() repository.MonitorTestResultRepository {
+	return t.monitor
+}
+
+func (t *TestRepository) APIContractRevisioner() repository.APIContractRevisioner {
+	return t.apiContractRevision
+}
+
+func (t *TestRepository) AWSAssumeRoleChainer() repository.AWSAssumeRoleChainer {
+	return t.awsAssumeRoleChainer
+}
+
+func (t *TestRepository) PorterApp() repository.PorterAppRepository {
+	return t.porterApp
+}
+
+func (t *TestRepository) PorterAppEvent() repository.PorterAppEventRepository {
+	return t.porterAppEvent
+}
+
+// DeploymentTarget returns a test DeploymentTargetRepository
+func (t *TestRepository) DeploymentTarget() repository.DeploymentTargetRepository {
+	return t.deploymentTarget
+}
+
+// AppRevision returns a test AppRevisionRepository
+func (t *TestRepository) AppRevision() repository.AppRevisionRepository {
+	return t.appRevision
+}
+
+// AppTemplate returns a test AppTemplateRepository
+func (t *TestRepository) AppTemplate() repository.AppTemplateRepository {
+	return t.appTemplate
+}
+
+// GithubWebhook returns a test GithubWebhookRepository
+func (t *TestRepository) GithubWebhook() repository.GithubWebhookRepository {
+	return t.githubWebhook
+}
+
+// Datastore returns a test DatastoreRepository
+func (t *TestRepository) Datastore() repository.DatastoreRepository {
+	return t.datastore
+}
+
+// AppInstance returns a test AppInstanceRepository
+func (t *TestRepository) AppInstance() repository.AppInstanceRepository {
+	return t.appInstance
+}
+
+// NewRepository returns a Repository which persists users in memory
+// and accepts a parameter that can trigger read/write errors
+func NewRepository(canQuery bool, failingMethods ...string) repository.Repository {
+	return &TestRepository{
+		user:                      NewUserRepository(canQuery, failingMethods...),
+		session:                   NewSessionRepository(canQuery, failingMethods...),
+		project:                   NewProjectRepository(canQuery, failingMethods...),
+		cluster:                   NewClusterRepository(canQuery),
+		helmRepo:                  NewHelmRepoRepository(canQuery),
+		registry:                  NewRegistryRepository(canQuery),
+		gitRepo:                   NewGitRepoRepository(canQuery),
+		gitActionConfig:           NewGitActionConfigRepository(canQuery),
+		invite:                    NewInviteRepository(canQuery),
+		release:                   NewReleaseRepository(canQuery),
+		environment:               NewEnvironmentRepository(),
+		authCode:                  NewAuthCodeRepository(canQuery),
+		dnsRecord:                 NewDNSRecordRepository(canQuery),
+		pwResetToken:              NewPWResetTokenRepository(canQuery),
+		infra:                     NewInfraRepository(canQuery),
+		kubeIntegration:           NewKubeIntegrationRepository(canQuery),
+		basicIntegration:          NewBasicIntegrationRepository(canQuery),
+		oidcIntegration:           NewOIDCIntegrationRepository(canQuery),
+		oauthIntegration:          NewOAuthIntegrationRepository(canQuery),
+		gcpIntegration:            NewGCPIntegrationRepository(canQuery),
+		awsIntegration:            NewAWSIntegrationRepository(canQuery),
+		azIntegration:             NewAzureIntegrationRepository(),
+		githubAppInstallation:     NewGithubAppInstallationRepository(canQuery),
+		githubAppOAuthIntegration: NewGithubAppOAuthIntegrationRepository(canQuery),
+		gitlabIntegration:         NewGitlabIntegrationRepository(canQuery),
+		gitlabAppOAuthIntegration: NewGitlabAppOAuthIntegrationRepository(canQuery),
+		slackIntegration:          NewSlackIntegrationRepository(canQuery),
+		notificationConfig:        NewNotificationConfigRepository(canQuery),
+		jobNotificationConfig:     NewJobNotificationConfigRepository(canQuery),
+		buildEvent:                NewBuildEventRepository(canQuery),
+		kubeEvent:                 NewKubeEventRepository(canQuery),
+		projectUsage:              NewProjectUsageRepository(canQuery),
+		onboarding:                NewProjectOnboardingRepository(canQuery),
+		ceToken:                   NewCredentialsExchangeTokenRepository(canQuery),
+		buildConfig:               NewBuildConfigRepository(canQuery),
+		database:                  NewDatabaseRepository(),
+		allowlist:                 NewAllowlistRepository(canQuery),
+		apiToken:                  NewAPITokenRepository(canQuery),
+		policy:                    NewPolicyRepository(canQuery),
+		tag:                       NewTagRepository(),
+		stack:                     NewStackRepository(),
+		monitor:                   NewMonitorTestResultRepository(canQuery),
+		apiContractRevision:       NewAPIContractRevisioner(),
+		awsAssumeRoleChainer:      NewAWSAssumeRoleChainer(),
+		porterApp:                 NewPorterAppRepository(canQuery, failingMethods...),
+		porterAppEvent:            NewPorterAppEventRepository(canQuery),
+		deploymentTarget:          NewDeploymentTargetRepository(),
+		appRevision:               NewAppRevisionRepository(),
+		appTemplate:               NewAppTemplateRepository(),
+		githubWebhook:             NewGithubWebhookRepository(),
+		datastore:                 NewDatastoreRepository(),
+		appInstance:               NewAppInstanceRepository(),
+	}
+}

+ 102 - 0
internal/repository/test/session.go

@@ -0,0 +1,102 @@
+package test
+
+import (
+	"errors"
+	"strings"
+
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+	"gorm.io/gorm"
+)
+
+const (
+	CreateSessionMethod string = "create_session_0"
+	SelectSessionMethod string = "select_session_0"
+)
+
+// SessionRepository uses gorm.DB for querying the database
+type SessionRepository struct {
+	canQuery       bool
+	failingMethods string
+	sessions       []*models.Session
+}
+
+// NewSessionRepository returns pointer to repo along with the db
+func NewSessionRepository(canQuery bool, failingMethods ...string) repository.SessionRepository {
+	return &SessionRepository{canQuery, strings.Join(failingMethods, ","), []*models.Session{}}
+}
+
+// CreateSession must take in Key, Data, and ExpiresAt as arguments.
+func (repo *SessionRepository) CreateSession(session *models.Session) (*models.Session, error) {
+	if !repo.canQuery || strings.Contains(repo.failingMethods, CreateSessionMethod) {
+		return nil, errors.New("Cannot write database")
+	}
+
+	// make sure key doesn't exist
+	for _, s := range repo.sessions {
+		if s.Key == session.Key {
+			return nil, errors.New("Cannot write database")
+		}
+	}
+
+	sessions := repo.sessions
+	sessions = append(sessions, session)
+	repo.sessions = sessions
+	session.ID = uint(len(repo.sessions))
+
+	return session, nil
+}
+
+// UpdateSession updates only the Data field using Key as selector.
+func (repo *SessionRepository) UpdateSession(session *models.Session) (*models.Session, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	var oldSession *models.Session
+
+	for _, s := range repo.sessions {
+		if s.Key == session.Key {
+			oldSession = s
+		}
+	}
+
+	if oldSession != nil {
+		oldSession.Data = session.Data
+
+		return oldSession, nil
+	}
+
+	return nil, gorm.ErrRecordNotFound
+}
+
+// DeleteSession deletes a session by Key
+func (repo *SessionRepository) DeleteSession(session *models.Session) (*models.Session, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	if int(session.ID-1) >= len(repo.sessions) || repo.sessions[session.ID-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(session.ID - 1)
+	repo.sessions[index] = nil
+
+	return session, nil
+}
+
+// SelectSession returns a session with matching key
+func (repo *SessionRepository) SelectSession(session *models.Session) (*models.Session, error) {
+	if !repo.canQuery || strings.Contains(repo.failingMethods, SelectSessionMethod) {
+		return nil, errors.New("Cannot write database")
+	}
+
+	for _, s := range repo.sessions {
+		if s.Key == session.Key {
+			return s, nil
+		}
+	}
+
+	return nil, gorm.ErrRecordNotFound
+}

+ 24 - 0
internal/repository/test/slack.go

@@ -0,0 +1,24 @@
+package test
+
+import (
+	ints "github.com/porter-dev/porter/internal/models/integrations"
+	"github.com/porter-dev/porter/internal/repository"
+)
+
+type SlackIntegrationRepository struct{}
+
+func NewSlackIntegrationRepository(canQuery bool) repository.SlackIntegrationRepository {
+	return &SlackIntegrationRepository{}
+}
+
+func (s *SlackIntegrationRepository) CreateSlackIntegration(slackInt *ints.SlackIntegration) (*ints.SlackIntegration, error) {
+	panic("not implemented") // TODO: Implement
+}
+
+func (s *SlackIntegrationRepository) ListSlackIntegrationsByProjectID(projectID uint) ([]*ints.SlackIntegration, error) {
+	panic("not implemented") // TODO: Implement
+}
+
+func (s *SlackIntegrationRepository) DeleteSlackIntegration(integrationID uint) error {
+	panic("not implemented") // TODO: Implement
+}

+ 68 - 0
internal/repository/test/stack.go

@@ -0,0 +1,68 @@
+package test
+
+import (
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+)
+
+type StackRepository struct{}
+
+func NewStackRepository() repository.StackRepository {
+	return &StackRepository{}
+}
+
+// CreateStack creates a new stack
+func (repo *StackRepository) CreateStack(stack *models.Stack) (*models.Stack, error) {
+	panic("unimplemented")
+}
+
+// ReadStack gets a stack specified by its string id
+func (repo *StackRepository) ListStacks(projectID, clusterID uint, namespace string) ([]*models.Stack, error) {
+	panic("unimplemented")
+}
+
+func (repo *StackRepository) ReadStackByID(projectID, stackID uint) (*models.Stack, error) {
+	panic("unimplemented")
+}
+
+// ReadStack gets a stack specified by its string id
+func (repo *StackRepository) ReadStackByStringID(projectID uint, stackID string) (*models.Stack, error) {
+	panic("unimplemented")
+}
+
+// DeleteStack creates a new stack
+func (repo *StackRepository) DeleteStack(stack *models.Stack) (*models.Stack, error) {
+	panic("unimplemented")
+}
+
+func (repo *StackRepository) UpdateStack(stack *models.Stack) (*models.Stack, error) {
+	panic("unimplemented")
+}
+
+func (repo *StackRepository) UpdateStackRevision(revision *models.StackRevision) (*models.StackRevision, error) {
+	panic("unimplemented")
+}
+
+func (repo *StackRepository) ReadStackRevision(stackRevisionID uint) (*models.StackRevision, error) {
+	panic("unimplemented")
+}
+
+func (repo *StackRepository) ReadStackRevisionByNumber(stackID uint, revisionNumber uint) (*models.StackRevision, error) {
+	panic("unimplemented")
+}
+
+func (repo *StackRepository) AppendNewRevision(revision *models.StackRevision) (*models.StackRevision, error) {
+	panic("unimplemented")
+}
+
+func (repo *StackRepository) ReadStackResource(resourceID uint) (*models.StackResource, error) {
+	panic("unimplemented")
+}
+
+func (repo *StackRepository) UpdateStackResource(resource *models.StackResource) (*models.StackResource, error) {
+	panic("unimplemented")
+}
+
+func (repo *StackRepository) ReadStackEnvGroupFirstMatch(projectID, clusterID uint, namespace, name string) (*models.StackEnvGroup, error) {
+	panic("unimplemented")
+}

+ 40 - 0
internal/repository/test/tag.go

@@ -0,0 +1,40 @@
+package test
+
+import (
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+)
+
+type TagRepository struct{}
+
+func NewTagRepository() repository.TagRepository {
+	return &TagRepository{}
+}
+
+func (repo *TagRepository) CreateTag(tag *models.Tag) (*models.Tag, error) {
+	panic("not implemented")
+}
+
+func (repo *TagRepository) ReadTagByNameAndProjectId(tagName string, projectID uint) (*models.Tag, error) {
+	panic("not implemented")
+}
+
+func (repo *TagRepository) ListTagsByProjectId(projectId uint) ([]*models.Tag, error) {
+	panic("not implemented")
+}
+
+func (repo *TagRepository) UpdateTag(tag *models.Tag) (*models.Tag, error) {
+	panic("not implemented")
+}
+
+func (repo *TagRepository) DeleteTag(id uint) error {
+	panic("not implemented")
+}
+
+func (repo *TagRepository) UnlinkTagsFromRelease(tags []string, release *models.Release) error {
+	panic("not implemented")
+}
+
+func (repo *TagRepository) LinkTagsToRelease(tags []string, release *models.Release) ([]*models.Tag, error) {
+	panic("not implemented")
+}

+ 129 - 0
internal/repository/test/usage.go

@@ -0,0 +1,129 @@
+package test
+
+import (
+	"errors"
+
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+	"gorm.io/gorm"
+)
+
+// ProjectUsageRepository implements repository.ProjectUsageRepository
+type ProjectUsageRepository struct {
+	canQuery bool
+	usages   []*models.ProjectUsage
+	caches   []*models.ProjectUsageCache
+}
+
+// NewProjectUsageRepository will return errors if canQuery is false
+func NewProjectUsageRepository(canQuery bool) repository.ProjectUsageRepository {
+	return &ProjectUsageRepository{
+		canQuery,
+		[]*models.ProjectUsage{},
+		[]*models.ProjectUsageCache{},
+	}
+}
+
+// CreateProjectUsage creates a new project usage limit
+func (repo *ProjectUsageRepository) CreateProjectUsage(
+	usage *models.ProjectUsage,
+) (*models.ProjectUsage, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	if usage == nil {
+		return nil, nil
+	}
+
+	repo.usages = append(repo.usages, usage)
+
+	return usage, nil
+}
+
+// CreateProjectUsage reads a project usage by project id
+func (repo *ProjectUsageRepository) ReadProjectUsage(
+	projID uint,
+) (*models.ProjectUsage, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	for _, pu := range repo.usages {
+		if pu != nil && pu.ProjectID == projID {
+			return pu, nil
+		}
+	}
+
+	return nil, gorm.ErrRecordNotFound
+}
+
+// UpdateProjectUsage modifies an existing ProjectUsage in the database
+func (repo *ProjectUsageRepository) UpdateProjectUsage(
+	usage *models.ProjectUsage,
+) (*models.ProjectUsage, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	if int(usage.ID-1) >= len(repo.usages) || repo.usages[usage.ID-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(usage.ID - 1)
+	repo.usages[index] = usage
+
+	return usage, nil
+}
+
+// CreateProjectUsageCache creates a new project usage cache
+func (repo *ProjectUsageRepository) CreateProjectUsageCache(
+	cache *models.ProjectUsageCache,
+) (*models.ProjectUsageCache, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	if cache == nil {
+		return nil, nil
+	}
+
+	repo.caches = append(repo.caches, cache)
+
+	return cache, nil
+}
+
+// CreateProjectUsageCache reads a project usage by project id
+func (repo *ProjectUsageRepository) ReadProjectUsageCache(
+	projID uint,
+) (*models.ProjectUsageCache, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	for _, puc := range repo.caches {
+		if puc != nil && puc.ProjectID == projID {
+			return puc, nil
+		}
+	}
+
+	return nil, gorm.ErrRecordNotFound
+}
+
+// UpdateProjectUsageCache modifies an existing ProjectUsageCache in the database
+func (repo *ProjectUsageRepository) UpdateProjectUsageCache(
+	cache *models.ProjectUsageCache,
+) (*models.ProjectUsageCache, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	if int(cache.ID-1) >= len(repo.caches) || repo.usages[cache.ID-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(cache.ID - 1)
+	repo.caches[index] = cache
+
+	return cache, nil
+}

+ 185 - 0
internal/repository/test/user.go

@@ -0,0 +1,185 @@
+package test
+
+import (
+	"errors"
+	"strings"
+
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+	"golang.org/x/crypto/bcrypt"
+	"gorm.io/gorm"
+)
+
+const (
+	CreateUserMethod      string = "create_user_0"
+	ReadUserMethod        string = "read_user_0"
+	ReadUserByEmailMethod string = "read_user_by_email_0"
+	DeleteUserMethod      string = "delete_user_0"
+)
+
+// UserRepository will return errors on queries if canQuery is false
+// and only stores a small set of users in-memory that are indexed by their
+// array index + 1
+type UserRepository struct {
+	canQuery       bool
+	failingMethods string
+	users          []*models.User
+}
+
+// NewUserRepository will return errors if canQuery is false
+func NewUserRepository(canQuery bool, failingMethods ...string) repository.UserRepository {
+	return &UserRepository{canQuery, strings.Join(failingMethods, ","), []*models.User{}}
+}
+
+// CreateUser adds a new User row to the Users table in array memory
+func (repo *UserRepository) CreateUser(user *models.User) (*models.User, error) {
+	if !repo.canQuery || strings.Contains(repo.failingMethods, CreateUserMethod) {
+		return nil, errors.New("Cannot write database")
+	}
+
+	// make sure email doesn't exist
+	for _, u := range repo.users {
+		if u.Email == user.Email {
+			return nil, errors.New("Cannot write database")
+		}
+	}
+
+	users := repo.users
+	users = append(users, user)
+	repo.users = users
+	user.ID = uint(len(repo.users))
+	return user, nil
+}
+
+// ReadUser finds a single user based on their unique id
+func (repo *UserRepository) ReadUser(id uint) (*models.User, error) {
+	if !repo.canQuery || strings.Contains(repo.failingMethods, ReadUserMethod) {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	if int(id-1) >= len(repo.users) || repo.users[id-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(id - 1)
+	return repo.users[index], nil
+}
+
+func (repo *UserRepository) ListUsersByIDs(ids []uint) ([]*models.User, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	resp := make([]*models.User, 0)
+
+	// find all roles matching
+	for _, user := range repo.users {
+		for _, userID := range ids {
+			if userID == user.ID {
+				resp = append(resp, user)
+			}
+		}
+	}
+
+	return resp, nil
+}
+
+// ReadUserByEmail finds a single user based on their unique email
+func (repo *UserRepository) ReadUserByEmail(email string) (*models.User, error) {
+	if !repo.canQuery || strings.Contains(repo.failingMethods, ReadUserByEmailMethod) {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	for _, u := range repo.users {
+		if u.Email == email {
+			return u, nil
+		}
+	}
+
+	return nil, gorm.ErrRecordNotFound
+}
+
+// ReadUserByGithubUserID finds a single user based on their github id field
+func (repo *UserRepository) ReadUserByGithubUserID(id int64) (*models.User, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	for _, u := range repo.users {
+		if u.GithubUserID == id && id != 0 {
+			return u, nil
+		}
+	}
+
+	return nil, gorm.ErrRecordNotFound
+}
+
+// ReadUserByGoogleUserID finds a single user based on their github id field
+func (repo *UserRepository) ReadUserByGoogleUserID(id string) (*models.User, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	for _, u := range repo.users {
+		if u.GoogleUserID == id && id != "" {
+			return u, nil
+		}
+	}
+
+	return nil, gorm.ErrRecordNotFound
+}
+
+// UpdateUser modifies an existing User in the database
+func (repo *UserRepository) UpdateUser(user *models.User) (*models.User, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	if int(user.ID-1) >= len(repo.users) || repo.users[user.ID-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(user.ID - 1)
+	oldUser := *repo.users[index]
+	repo.users[index] = user
+	user.Email = oldUser.Email
+	user.Password = oldUser.Password
+
+	return user, nil
+}
+
+// DeleteUser deletes a single user using their unique id
+func (repo *UserRepository) DeleteUser(user *models.User) (*models.User, error) {
+	if !repo.canQuery || strings.Contains(repo.failingMethods, DeleteUserMethod) {
+		return nil, errors.New("Cannot write database")
+	}
+
+	if int(user.ID-1) >= len(repo.users) || repo.users[user.ID-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(user.ID - 1)
+	repo.users[index] = nil
+
+	return user, nil
+}
+
+// CheckPassword checks the input password is correct for the provided user id.
+func (repo *UserRepository) CheckPassword(id int, pwd string) (bool, error) {
+	if !repo.canQuery {
+		return false, errors.New("Cannot write database")
+	}
+
+	if int(id-1) >= len(repo.users) || repo.users[id-1] == nil {
+		return false, gorm.ErrRecordNotFound
+	}
+
+	index := int(id - 1)
+	user := *repo.users[index]
+
+	if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(pwd)); err != nil {
+		return false, err
+	}
+
+	return true, nil
+}