Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions apperrors/apperrors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package apperrors

import (
"encoding/json"
"errors"
"net/http"

l "github.com/sirupsen/logrus"
)

// ErrorStruct - struct used to convert error messages into required JSON format
type ErrorStruct struct {
Message string `json:"message,omitempty"` //Error Message
Status int `json:"status,omitempty"` //HTTP Response status code
}

// Error - prints out an error
func Error(appError error, msg string, triggeringError error) {
l.WithFields(l.Fields{"appError": appError, "message": msg}).Error(triggeringError)
}

// Warn - for warnings
func Warn(appError error, msg string, triggeringError error) {
l.WithFields(l.Fields{"appError": appError, "message": msg}).Warn(triggeringError)
}

// JSONError - This function writes out an error response with the status
// header passed in
func JSONError(rw http.ResponseWriter, status int, err error) {

errObj := ErrorStruct{
Message: err.Error(),
Status: status,
}

errJSON, err := json.Marshal(&errObj)
if err != nil {
Warn(err, "Error in AppErrors marshalling JSON", err)
}
rw.WriteHeader(status)
rw.Header().Add("Content-Type", "application/json")
rw.Write(errJSON)
return
}

// ErrRecordNotFound - for when a database record isn't found
var ErrRecordNotFound = errors.New("Database record not found")

// ErrInvalidToken - used when a JSON Web Token ("JWT") cannot be validated
// by the JWT library
var ErrInvalidToken = errors.New("Invalid Token")

// ErrSignedString - failed to sign the token string
var ErrSignedString = errors.New("Failed to sign token string")

// ErrMissingAuthHeader - When the HTTP request doesn't contain an 'Authorization' header
var ErrMissingAuthHeader = errors.New("Missing Auth header")

// ErrJSONParseFail - If json.Unmarshal or json.Marshal returns an error
var ErrJSONParseFail = errors.New("Failed to parse JSON response (likely not valid JSON)")

// ErrNoSigningKey - there isn't a signing key defined in the app configuration
var ErrNoSigningKey = errors.New("no JWT signing key specified; cannot authenticate users. Define JWT_SECRET in application.yml and restart")

// ErrFailedToCreate - Record Creation Failed
var ErrFailedToCreate = errors.New("Failed to create database record")

// ErrUnknown - Generic Error For Unknown Errors
var ErrUnknown = errors.New("unknown/unexpected error has occurred")
33 changes: 27 additions & 6 deletions config/config.go
Original file line number Diff line number Diff line change
@@ -1,45 +1,64 @@
package config

import (
"errors"
"fmt"
"strconv"

"github.com/spf13/viper"
)

var (
appName string
appPort int
appName string
appPort int
jwtKey string
jwtExpiryDurationHours int
)

func Load() {
viper.SetDefault("APP_NAME", "app")

viper.SetDefault("APP_NAME", "e-commerce")
viper.SetDefault("APP_PORT", "8002")

viper.SetConfigName("application")
viper.SetConfigName("application") // name of config file (without extension)
viper.SetConfigType("yaml")
viper.AddConfigPath("./")
viper.AddConfigPath("./..")
viper.AddConfigPath("./../..")
viper.ReadInConfig()
viper.AutomaticEnv()

// Check for the presence of JWT_KEY and JWT_EXPIRY_DURATION_HOURS
JWTKey()
JWTExpiryDurationHours()
}

// AppName - returns the app name
func AppName() string {
if appName == "" {
appName = ReadEnvString("APP_NAME")
}
return appName
}

// AppPort - returns application http port
func AppPort() int {
if appPort == 0 {
appPort = ReadEnvInt("APP_PORT")
}
return appPort
}

// JWTKey - returns the JSON Web Token key
func JWTKey() []byte {
return []byte(ReadEnvString("JWT_SECRET"))
}

// JWTExpiryDurationHours - returns duration for jwt expiry in int
func JWTExpiryDurationHours() int {
return ReadEnvInt("JWT_EXPIRY_DURATION_HOURS")
}

// ReadEnvInt - reads an environment variable as an integer
func ReadEnvInt(key string) int {
checkIfSet(key)
v, err := strconv.Atoi(viper.GetString(key))
Expand All @@ -49,19 +68,21 @@ func ReadEnvInt(key string) int {
return v
}

// ReadEnvString - reads an environment variable as a string
func ReadEnvString(key string) string {
checkIfSet(key)
return viper.GetString(key)
}

// ReadEnvBool - reads environment variable as a boolean
func ReadEnvBool(key string) bool {
checkIfSet(key)
return viper.GetBool(key)
}

func checkIfSet(key string) {
if !viper.IsSet(key) {
err := errors.New(fmt.Sprintf("Key %s is not set", key))
err := fmt.Errorf("Key %s is not set", key)
panic(err)
}
}
35 changes: 35 additions & 0 deletions db/cart.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package db

import (
"context"

"github.com/lib/pq"
logger "github.com/sirupsen/logrus"
)

// CartProduct is rquirement of frontend as json response
type CartProduct struct {
Id int `db:"product_id" json:"id"`
Name string `db:"product_name" json:"product_title"`
Quantity int `db:"quantity" json:"quantity"`
Category string `db:"category_name" json:"category,omitempty"`
Price float64 `db:"price" json:"product_price"`
Description string `db:"description" json:"description"`
ImageUrls pq.StringArray `db:"image_urls" json:"image_url,*"`
Discount float32 `db:"discount" json:"discount"`
Tax float32 `db:"tax" json:"tax"`
}

const (
joinCartProductQuery = `SELECT cart.product_id, products.name as product_name, cart.quantity, category.name as category_name, products.price , products.description, products.image_urls, products.discount, products.tax from cart inner join products on cart.product_id=products.id inner join category on category.id=products.category_id where cart.id=$1 ORDER BY cart.product_id ASC;`
)

// *pgStore because deps.Store.GetCart - deps is of struct Dependencies - vch is assigned to db conn obj
func (s *pgStore) GetCart(ctx context.Context, user_id int) (cart_products []CartProduct, err error) {
err = s.db.Select(&cart_products, joinCartProductQuery, user_id)
if err != nil {
logger.WithField("err", err.Error()).Error("Error fetching data from cart")
return
}
return
}
77 changes: 77 additions & 0 deletions db/cart_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package db

import (
"context"
"errors"

"github.com/DATA-DOG/go-sqlmock"
"github.com/jmoiron/sqlx"
"github.com/lib/pq"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)

type CartTestSuite struct {
suite.Suite
dbStore Storer
db *sqlx.DB
sqlmock sqlmock.Sqlmock
}

var image_urls = pq.StringArray([]string{"url1", "url2"})

var expectedCart = CartProduct{
Id: 1,
Name: "laptop",
Quantity: 1,
Category: "electronics",
Price: 202,
Description: "description",
ImageUrls: image_urls,
Discount: 20,
Tax: 5,
}

func (suite *CartTestSuite) SetupTest() {
dbStore, dbConn, sqlmock := InitMockDB()
suite.dbStore = dbStore
suite.db = dbConn
suite.sqlmock = sqlmock
mockedRows = suite.getMockedRows()
}

func (suite *CartTestSuite) TearDownTest() {
suite.db.Close()
}

func (suite *CartTestSuite) getMockedRows() (mockedRows *sqlmock.Rows) {
mockedRows = suite.sqlmock.NewRows([]string{"product_id", "product_name", "quantity", "category_name", "price", "description", "image_urls", "discount", "tax"}).
AddRow(1, "laptop", 1, "electronics", 202, "description", image_urls, 20, 5)
return
}

func (suite *CartTestSuite) TestGetCartSuccess() {

suite.sqlmock.ExpectQuery(joinCartProductQuery).
WithArgs(1).
WillReturnRows(mockedRows)

cart, err := suite.dbStore.GetCart(context.Background(), expectedCart.Id)

assert.Nil(suite.T(), err)
assert.Equal(suite.T(), []CartProduct{expectedCart}, cart)
}

func (suite *CartTestSuite) TestCartFailure() {

suite.db.Close()
//Close connection to test failure case

suite.sqlmock.ExpectQuery(joinCartProductQuery).
WillReturnRows(mockedRows)

_, err := suite.dbStore.GetCart(context.Background(), expectedCart.Id)

assert.NotNil(suite.T(), err)
assert.Equal(suite.T(), errors.New("sql: database is closed"), err)
}
38 changes: 38 additions & 0 deletions db/common_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package db

import (
"testing"
"time"

"github.com/DATA-DOG/go-sqlmock"
"github.com/jmoiron/sqlx"
logger "github.com/sirupsen/logrus"
"github.com/stretchr/testify/suite"
)

var (
now time.Time
mockedRows *sqlmock.Rows
)

func InitMockDB() (s Storer, sqlConn *sqlx.DB, sqlmockInstance sqlmock.Sqlmock) {

// sqlmock.New() gives {error : not able to match sql queries} ,so adding these parameters (sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) to sqlmock.New allows complex queries like "Join" to be matched
mockDB, sqlmock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
if err != nil {
logger.WithField("err:", err).Error("error initializing mock db")
return
}

sqlmockInstance = sqlmock
sqlxDB := sqlx.NewDb(mockDB, "sqlmock")

var pgStoreConn pgStore
pgStoreConn.db = sqlxDB

return &pgStoreConn, sqlxDB, sqlmockInstance
}

func TestExampleTestSuite(t *testing.T) {
suite.Run(t, new(CartTestSuite))
}
8 changes: 5 additions & 3 deletions db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import (

type Storer interface {
ListUsers(context.Context) ([]User, error)
//Create(context.Context, User) error
//GetUser(context.Context) (User, error)
//Delete(context.Context, string) error
AuthenticateUser(context.Context, User) (User, error)
GetUser(context.Context, int) (User, error)
CreateBlacklistedToken(context.Context, BlacklistedToken) error
CheckBlacklistedToken(context.Context, string) (bool, int)
GetCart(context.Context, int) ([]CartProduct, error)
}
26 changes: 26 additions & 0 deletions db/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,29 @@ func (m *DBMockStore) ListUsers(ctx context.Context) (users []User, err error) {
args := m.Called(ctx)
return args.Get(0).([]User), args.Error(1)
}

//deps.store.GetCart(ctx,id)
func (m *DBMockStore) GetCart(ctx context.Context, user_id int) (cart_products []CartProduct, err error) {
args := m.Called(ctx, user_id)
return args.Get(0).([]CartProduct), args.Error(1)
}

func (m *DBMockStore) AuthenticateUser(ctx context.Context, u User) (user User, err error) {
args := m.Called(ctx, u)
return args.Get(0).(User), args.Error(1)
}

func (m *DBMockStore) CheckBlacklistedToken(ctx context.Context, token string) (bool, int) {
args := m.Called(ctx, token)
return args.Get(0).(bool), args.Get(1).(int)
}

func (m *DBMockStore) CreateBlacklistedToken(ctx context.Context, token BlacklistedToken) (err error) {
args := m.Called(ctx, token)
return args.Get(0).(error)
}

func (m *DBMockStore) GetUser(ctx context.Context, id int) (user User, err error) {
args := m.Called(ctx, id)
return args.Get(0).(User), args.Error(1)
}
Loading