diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..098bf8e --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module github.com/Helgart/stock-persistence + +go 1.22.1 + +require ( + github.com/DATA-DOG/go-sqlmock v1.5.2 + github.com/lib/pq v1.10.9 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..3bcb6a3 --- /dev/null +++ b/go.sum @@ -0,0 +1,5 @@ +github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= +github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= +github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= diff --git a/infrastructure/database/connection.go b/infrastructure/database/connection.go new file mode 100644 index 0000000..c6d3804 --- /dev/null +++ b/infrastructure/database/connection.go @@ -0,0 +1,60 @@ +package database + +import ( + "database/sql" + "fmt" +) + +// connexionErrorMessage represents the error message format for a database connection error. +const connexionErrorMessage = "Database connexion error (%s@%s:%d/%s) : %s" + +// Database is a type that represents a database connection. +type Database struct { + connexion *sql.DB +} + +// DatabaseParameters represents the parameters used to establish a database connection. +type DatabaseParameters struct { + Host string + Port uint + User string + Password string + Name string +} + +// ConnexionError represents an error that occurred during database connection. +type ConnexionError struct { + PreviousError error + PsqlInfo string + DatabaseParameters DatabaseParameters +} + +// Error returns the error message representation of a ConnexionError. +func (connexionError ConnexionError) Error() string { + return fmt.Sprintf( + connexionErrorMessage, + connexionError.DatabaseParameters.User, + connexionError.DatabaseParameters.Host, + connexionError.DatabaseParameters.Port, + connexionError.DatabaseParameters.Name, + connexionError.PreviousError.Error(), + ) +} + +// NewDatabase creates a new Database struct with the provided connection. +// It takes a pointer to sql.DB and returns a Database instance. +func NewDatabase(connexion *sql.DB) Database { + return Database{connexion: connexion} +} + +// Ping pings the database to check its availability. +// It returns an error if the ping fails. +func (database *Database) Ping() error { + err := database.connexion.Ping() + + if err != nil { + return err + } + + return nil +} diff --git a/infrastructure/database/connection_test.go b/infrastructure/database/connection_test.go new file mode 100644 index 0000000..f8b1e08 --- /dev/null +++ b/infrastructure/database/connection_test.go @@ -0,0 +1,80 @@ +package database + +import ( + "database/sql" + "fmt" + "testing" + + sqlmock "github.com/DATA-DOG/go-sqlmock" +) + +func TestDatabase_Ping(t *testing.T) { + tests := []struct { + name string + setup func(mock sqlmock.Sqlmock) + wantErr bool + }{ + { + name: "Success", + setup: func(mock sqlmock.Sqlmock) { + mock.ExpectPing().WillReturnError(nil) + }, + wantErr: false, + }, + { + name: "Failure", + setup: func(mock sqlmock.Sqlmock) { + mock.ExpectPing().WillReturnError(sql.ErrConnDone) + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + db, mock, err := sqlmock.New(sqlmock.MonitorPingsOption(true)) + + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + + tt.setup(mock) + + database := NewDatabase(db) + + if err := database.Ping(); (err != nil) != tt.wantErr { + t.Errorf("Database.Ping() error = %v, wantErr %v", err, tt.wantErr) + } + + if err := mock.ExpectationsWereMet(); err != nil { + t.Errorf("Database.Ping() there were unfulfilled expectations: %s", err) + } + }) + } +} + +func TestConnexionError_Error(t *testing.T) { + tests := []struct { + name string + err ConnexionError + want string + }{ + { + name: "Error Message", + err: ConnexionError{ + PreviousError: fmt.Errorf("previousError"), + PsqlInfo: "psqlInfo", + DatabaseParameters: DatabaseParameters{User: "user", Host: "host", Port: 9999, Name: "name"}, + }, + want: fmt.Sprintf(connexionErrorMessage, "user", "host", 9999, "name", "previousError"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.err.Error(); got != tt.want { + t.Errorf("ConnexionError.Error() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/infrastructure/database/postgres/connection.go b/infrastructure/database/postgres/connection.go new file mode 100644 index 0000000..baf75a1 --- /dev/null +++ b/infrastructure/database/postgres/connection.go @@ -0,0 +1,43 @@ +package postgres + +import ( + "database/sql" + "fmt" + "github.com/Helgart/stock-persistence/infrastructure/database" + + _ "github.com/lib/pq" +) + +const infoFormat = "host=%s port=%d user=%s password=%s dbname=%s sslmode=disable" +const driver = "postgres" + +func NewDatabase(databaseParameters database.DatabaseParameters) (database.Database, error) { + psqlInfo := fmt.Sprintf( + infoFormat, + databaseParameters.Host, + databaseParameters.Port, + databaseParameters.User, + databaseParameters.Password, + databaseParameters.Name, + ) + + connexion, err := sql.Open(driver, psqlInfo) + + if err != nil { + return database.Database{}, database.ConnexionError{ + PreviousError: err, + PsqlInfo: psqlInfo, + DatabaseParameters: databaseParameters, + } + } + + if err := connexion.Ping(); err != nil { + return database.Database{}, database.ConnexionError{ + PreviousError: err, + PsqlInfo: psqlInfo, + DatabaseParameters: databaseParameters, + } + } + + return database.NewDatabase(connexion), nil +}