package dal import ( "database/sql" "database/sql/driver" "reflect" "testing" "time" "git.rosemyrtle.work/personal-finance/server/internal/entity" "git.rosemyrtle.work/personal-finance/server/internal/golang" "github.com/DATA-DOG/go-sqlmock" "github.com/google/uuid" "github.com/shopspring/decimal" ) func TestDalImpl_Transaction(t *testing.T) { db, mock, err := sqlmock.New() if err != nil { t.Fatal(err) } date := time.Now() type fields struct { Db *sql.DB } type args struct { transactionId int64 } tests := []struct { name string fields fields args args mocks [][]driver.Value want *entity.Transaction wantErr bool }{ { "SelectTransaction", fields{db}, args{1}, [][]driver.Value{ {1, date, "income", 1000, nil}, }, &entity.Transaction{Id: 1, Date: date, Description: "income", Value: decimal.NewFromInt(1000)}, false, }, { "SelectTransactionWithCategory", fields{db}, args{1}, [][]driver.Value{ {1, date, "income", 1000, "C1"}, }, &entity.Transaction{Id: 1, Date: date, Description: "income", Value: decimal.NewFromInt(1000), Category: golang.Ptr("C1")}, false, }, { "SelectNoTransaction", fields{db}, args{2}, nil, nil, false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { d := &DalImpl{ Db: tt.fields.Db, } mock. ExpectQuery("^SELECT.*id.*date.*description.*amount.*name.*WHERE.*id.*= \\?"). WithArgs(tt.args.transactionId). WillReturnRows( mock.NewRows([]string{"id", "date", "description", "amount", "category"}).AddRows(tt.mocks...), ) got, err := d.Transaction(tt.args.transactionId) if (err != nil) != tt.wantErr { t.Errorf("DalImpl.Transaction() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("DalImpl.Transaction() = %v, want %v", got, tt.want) } }) } } func TestDalImpl_Transactions(t *testing.T) { db, mock, err := sqlmock.New() if err != nil { t.Fatal(err) } date := time.Now() type fields struct { Db *sql.DB } type args struct { limit, offset int description *string category *string } tests := []struct { name string fields fields args args mocks [][]driver.Value want entity.Transactions wantErr bool }{ { "SelectTransactions", fields{db}, args{limit: 30, offset: 30, description: nil, category: nil}, [][]driver.Value{ {1, date, "income", 1000, nil, 2}, {2, date, "expense", -10.50, nil, 2}, }, entity.Transactions{ {Id: 1, Date: date, Description: "income", Value: decimal.NewFromInt(1000)}, {Id: 2, Date: date, Description: "expense", Value: decimal.NewFromFloat(-10.50)}, }, false, }, { "SelectTransactionsWithDescription", fields{db}, args{limit: 30, offset: 30, description: golang.Ptr("income"), category: nil}, [][]driver.Value{ {1, date, "income #1", 1000, "C1", 2}, {2, date, "income #2", -10.50, nil, 2}, }, entity.Transactions{ {Id: 1, Date: date, Description: "income #1", Value: decimal.NewFromInt(1000), Category: golang.Ptr("C1")}, {Id: 2, Date: date, Description: "income #2", Value: decimal.NewFromFloat(-10.50)}, }, false, }, { "SelectTransactionsWithCategory", fields{db}, args{limit: 30, offset: 30, description: nil, category: golang.Ptr("C1")}, [][]driver.Value{ {1, date, "income", 1000, "C1", 2}, {2, date, "expense", -10.50, "C1", 2}, }, entity.Transactions{ {Id: 1, Date: date, Description: "income", Value: decimal.NewFromInt(1000), Category: golang.Ptr("C1")}, {Id: 2, Date: date, Description: "expense", Value: decimal.NewFromFloat(-10.50), Category: golang.Ptr("C1")}, }, false, }, { "SelectNoTransactions", fields{db}, args{limit: 30, offset: 30, description: nil, category: nil}, nil, nil, false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { dal := &DalImpl{ Db: tt.fields.Db, } args := []driver.Value{} stmt := "^SELECT.*id.*date.*description.*amount.*name.*count\\(\\*\\) OVER\\(\\) AS total_count" if tt.args.description != nil { stmt += ".*WHERE.*description.*ILIKE \\?" args = append(args, "%"+*tt.args.description+"%") } if tt.args.category != nil { stmt += ".*WHERE.*name.*ILIKE \\?" args = append(args, "%"+*tt.args.category+"%") } stmt += ".*ORDER BY.*date.*DESC LIMIT \\? OFFSET \\?" args = append(args, tt.args.limit, tt.args.offset) mock. ExpectQuery(stmt). WithArgs(args...). WillReturnRows( mock.NewRows([]string{"id", "date", "description", "amount", "category", "total_count"}).AddRows(tt.mocks...), ) got, _, err := dal.Transactions(tt.args.limit, tt.args.offset, tt.args.description, tt.args.category) if (err != nil) != tt.wantErr { t.Errorf("DalImpl.Transactions() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("DalImpl.Transactions() = %v, want %v", got, tt.want) } }) } } func TestDalImpl_Bank(t *testing.T) { db, mock, err := sqlmock.New() if err != nil { t.Fatal(err) } uuid := uuid.New() type fields struct { Db *sql.DB } type args struct { bankId string rows [][]driver.Value } tests := []struct { name string fields fields args args want *entity.Bank wantErr bool }{ { "200", fields{db}, args{ "Bank A", [][]driver.Value{ {"Bank A", "Bank A", uuid.String()}, }}, &entity.Bank{Id: "Bank A", Name: "Bank A", NordigenId: uuid}, false, }, {"404", fields{db}, args{"Bank B", nil}, nil, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { d := &DalImpl{ Db: tt.fields.Db, } mock. ExpectQuery("^SELECT.*name.*name.*requisition_id.*WHERE.*name.*= \\?"). WithArgs(tt.args.bankId). WillReturnRows( mock. NewRows([]string{"name", "name", "requisition_id"}). AddRows(tt.args.rows...), ) got, err := d.Bank(tt.args.bankId) if (err != nil) != tt.wantErr { t.Errorf("DalImpl.Bank() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("DalImpl.Bank() = %v, want %v", got, tt.want) } }) } } func TestDalImpl_Banks(t *testing.T) { db, mock, err := sqlmock.New() if err != nil { t.Fatal(err) } uuid := uuid.New() type fields struct { Db *sql.DB } type args struct { rows [][]driver.Value } tests := []struct { name string fields fields args args want entity.Banks wantErr bool }{ { "200", fields{db}, args{[][]driver.Value{ {"Bank A", "Bank A", uuid.String()}, {"Bank B", "Bank B", uuid.String()}, }}, entity.Banks{ {Id: "Bank A", Name: "Bank A", NordigenId: uuid}, {Id: "Bank B", Name: "Bank B", NordigenId: uuid}, }, false, }, {"204", fields{db}, args{}, nil, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { d := &DalImpl{ Db: tt.fields.Db, } mock. ExpectQuery("^SELECT.*name.*name.*requisition_id"). WithoutArgs(). WillReturnRows( mock. NewRows([]string{"name", "name", "requisition_id"}). AddRows(tt.args.rows...), ) got, err := d.Banks() if (err != nil) != tt.wantErr { t.Errorf("DalImpl.Banks() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("DalImpl.Banks() = %v, want %v", got, tt.want) } }) } } func TestDalImpl_InsertTransaction(t *testing.T) { db, mock, err := sqlmock.New() if err != nil { t.Fatal(err) } date := time.Now() type fields struct { Db *sql.DB } type args struct { t entity.Transaction } tests := []struct { name string fields fields args args want entity.Transaction wantErr bool }{ { "201", fields{db}, args{entity.Transaction{Id: entity.InvalidId, Date: date, Description: "freedom", Value: decimal.NewFromInt(9000)}}, entity.Transaction{Id: 1, Date: date, Description: "freedom", Value: decimal.NewFromInt(9000)}, false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { dal := &DalImpl{ Db: tt.fields.Db, } mock. ExpectQuery(` INSERT INTO.*.*amount.*date.*description.* VALUES.*\?, \?, \?.* RETURNING.*id`). WithArgs(tt.args.t.Value, tt.args.t.Date, tt.args.t.Description). WillReturnRows( mock.NewRows([]string{"id"}). AddRows([]driver.Value{tt.want.Id}), ) got, err := dal.InsertTransaction(tt.args.t) if (err != nil) != tt.wantErr { t.Errorf("DalImpl.InsertTransaction() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("DalImpl.InsertTransaction() = %v, want %v", got, tt.want) } }) } } func TestDalImpl_UpdateTransaction(t *testing.T) { db, mock, err := sqlmock.New() if err != nil { t.Fatal(err) } type fields struct { Db *sql.DB } type args struct { id entity.TransactionId category *entity.CategoryName } tests := []struct { name string fields fields args args mocks driver.Result want bool wantErr bool }{ { "SuccessfulUpdate", fields{db}, args{id: entity.InvalidId, category: golang.Ptr("C1")}, sqlmock.NewResult(0, 1), true, false, }, { "UnsuccessfulUpdate", fields{db}, args{id: entity.InvalidId, category: golang.Ptr("C1")}, sqlmock.NewResult(0, 0), false, true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { dal := &DalImpl{ Db: tt.fields.Db, } mock. ExpectExec(` UPDATE.*transactions_categorized.* SET.*name.*= ?.* WHERE.*id.*= ?`). WithArgs(tt.args.category, tt.args.id). WillReturnResult(tt.mocks) got, err := dal.UpdateTransaction(tt.args.id, tt.args.category) if (err != nil) != tt.wantErr { t.Errorf("DalImpl.UpdateTransaction() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("DalImpl.UpdateTransaction() = %v, want %v", got, tt.want) } }) } } func TestDalImpl_TransactionExists(t *testing.T) { db, mock, err := sqlmock.New() if err != nil { t.Fatal(err) } type fields struct { Db *sql.DB } type args struct { id uint64 } tests := []struct { name string fields fields args args want bool wantErr bool }{ {"TransactionExists", fields{db}, args{1}, true, false}, {"TransactionNotExists", fields{db}, args{1}, false, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { dal := &DalImpl{ Db: tt.fields.Db, } mock. ExpectQuery("EXISTS\\(SELECT 1 FROM pfbudget\\.transactions WHERE id = \\?\\)"). WithArgs(tt.args.id). WillReturnRows( mock.NewRows([]string{"exists"}). AddRows([]driver.Value{tt.want}), ) got, err := dal.TransactionExists(tt.args.id) if (err != nil) != tt.wantErr { t.Errorf("DalImpl.TransactionExists() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { t.Errorf("DalImpl.TransactionExists() = %v, want %v", got, tt.want) } }) } } func TestDalImpl_Categories(t *testing.T) { db, mock, err := sqlmock.New() if err != nil { t.Fatal(err) } type fields struct { Db *sql.DB } tests := []struct { name string fields fields mocks [][]driver.Value want entity.Categories wantErr bool }{ { "SelectCategories", fields{db}, [][]driver.Value{{"C1", "G1"}}, entity.Categories{{Name: "C1", Group: golang.Ptr("G1")}}, false, }, { "SelectMutipleCategories", fields{db}, [][]driver.Value{{"C1", "G1"}, {"C2", nil}}, entity.Categories{{Name: "C1", Group: golang.Ptr("G1")}, {Name: "C2"}}, false, }, { "SelectNoCategories", fields{db}, [][]driver.Value{}, nil, false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { dal := &DalImpl{ Db: tt.fields.Db, } mock. ExpectQuery(`SELECT.*name.*group.*FROM.*categories`). WithoutArgs(). WillReturnRows(mock.NewRows([]string{"name", "group"}).AddRows(tt.mocks...)) got, err := dal.Categories() if (err != nil) != tt.wantErr { t.Errorf("DalImpl.Categories() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("DalImpl.Categories() = %v, want %v", got, tt.want) } }) } }