package main

import (
	"fmt"
	"testing"

	"github.com/DATA-DOG/go-sqlmock"
)

// will test that order with a different status, cannot be cancelled
func TestShouldNotCancelOrderWithNonPendingStatus(t *testing.T) {
	// open database stub
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Errorf("An error '%s' was not expected when opening a stub database connection", err)
	}
	defer db.Close()

	// columns are prefixed with "o" since we used sqlstruct to generate them
	columns := []string{"o_id", "o_status"}
	// expect transaction begin
	mock.ExpectBegin()
	// expect query to fetch order and user, match it with regexp
	mock.ExpectQuery("SELECT (.+) FROM orders AS o INNER JOIN users AS u (.+) FOR UPDATE").
		WithArgs(1).
		WillReturnRows(sqlmock.NewRows(columns).FromCSVString("1,1"))
	// expect transaction rollback, since order status is "cancelled"
	mock.ExpectRollback()

	// run the cancel order function
	err = cancelOrder(1, db)
	if err != nil {
		t.Errorf("Expected no error, but got %s instead", err)
	}
	// we make sure that all expectations were met
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Errorf("there were unfulfilled expectations: %s", err)
	}
}

// will test order cancellation
func TestShouldRefundUserWhenOrderIsCancelled(t *testing.T) {
	// open database stub
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Errorf("An error '%s' was not expected when opening a stub database connection", err)
	}
	defer db.Close()

	// columns are prefixed with "o" since we used sqlstruct to generate them
	columns := []string{"o_id", "o_status", "o_value", "o_reserved_fee", "u_id", "u_balance"}
	// expect transaction begin
	mock.ExpectBegin()
	// expect query to fetch order and user, match it with regexp
	mock.ExpectQuery("SELECT (.+) FROM orders AS o INNER JOIN users AS u (.+) FOR UPDATE").
		WithArgs(1).
		WillReturnRows(sqlmock.NewRows(columns).AddRow(1, 0, 25.75, 3.25, 2, 10.00))
	// expect user balance update
	mock.ExpectPrepare("UPDATE users SET balance").ExpectExec().
		WithArgs(25.75+3.25, 2).                  // refund amount, user id
		WillReturnResult(sqlmock.NewResult(0, 1)) // no insert id, 1 affected row
	// expect order status update
	mock.ExpectPrepare("UPDATE orders SET status").ExpectExec().
		WithArgs(ORDER_CANCELLED, 1).             // status, id
		WillReturnResult(sqlmock.NewResult(0, 1)) // no insert id, 1 affected row
	// expect a transaction commit
	mock.ExpectCommit()

	// run the cancel order function
	err = cancelOrder(1, db)
	if err != nil {
		t.Errorf("Expected no error, but got %s instead", err)
	}
	// we make sure that all expectations were met
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Errorf("there were unfulfilled expectations: %s", err)
	}
}

// will test order cancellation
func TestShouldRollbackOnError(t *testing.T) {
	// open database stub
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Errorf("An error '%s' was not expected when opening a stub database connection", err)
	}
	defer db.Close()

	// expect transaction begin
	mock.ExpectBegin()
	// expect query to fetch order and user, match it with regexp
	mock.ExpectQuery("SELECT (.+) FROM orders AS o INNER JOIN users AS u (.+) FOR UPDATE").
		WithArgs(1).
		WillReturnError(fmt.Errorf("Some error"))
	// should rollback since error was returned from query execution
	mock.ExpectRollback()

	// run the cancel order function
	err = cancelOrder(1, db)
	// error should return back
	if err == nil {
		t.Error("Expected error, but got none")
	}
	// we make sure that all expectations were met
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Errorf("there were unfulfilled expectations: %s", err)
	}
}