Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pfs-116 FE only for downloading a report #3

Merged
merged 8 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from 7 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
41 changes: 35 additions & 6 deletions internal/api/api_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package api
import (
"context"
"fmt"
"io"
"net/http"
)

Expand All @@ -21,18 +22,22 @@ func (e ClientError) Error() string {
return string(e)
}

func NewApiClient(httpClient HTTPClient) *ApiClient {
return &ApiClient{
http: httpClient,
}
func NewClient(httpClient HTTPClient, siriusUrl string, backendUrl string) (*Client, error) {
return &Client{
http: httpClient,
siriusUrl: siriusUrl,
backendUrl: backendUrl,
}, nil
}

type HTTPClient interface {
Do(req *http.Request) (*http.Response, error)
}

type ApiClient struct {
http HTTPClient
type Client struct {
http HTTPClient
siriusUrl string
backendUrl string
}

type StatusError struct {
Expand All @@ -48,3 +53,27 @@ func (e StatusError) Error() string {
func (e StatusError) Data() interface{} {
return e
}

func (c *Client) newBackendRequest(ctx Context, method, path string, body io.Reader) (*http.Request, error) {
req, err := http.NewRequestWithContext(ctx.Context, method, c.backendUrl+path, body)
if err != nil {
return nil, err
}

for _, c := range ctx.Cookies {
req.AddCookie(c)
}

req.Header.Add("OPG-Bypass-Membrane", "1")
req.Header.Add("X-XSRF-TOKEN", ctx.XSRFToken)

return req, err
}

func newStatusError(resp *http.Response) StatusError {
return StatusError{
Code: resp.StatusCode,
URL: resp.Request.URL.String(),
Method: resp.Request.Method,
}
}
46 changes: 46 additions & 0 deletions internal/api/api_client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package api

import (
"context"
"net/http"
"testing"

"github.com/stretchr/testify/assert"
)

type MockClient struct {
}

var (
GetDoFunc func(req *http.Request) (*http.Response, error)
)

func (m *MockClient) Do(req *http.Request) (*http.Response, error) {
return GetDoFunc(req)
}

func getContext(cookies []*http.Cookie) Context {
return Context{
Context: context.Background(),
Cookies: cookies,
XSRFToken: "abcde",
}
}

func TestClientError(t *testing.T) {
assert.Equal(t, "message", ClientError("message").Error())
}

func TestStatusError(t *testing.T) {
req, _ := http.NewRequest(http.MethodPost, "/some/url", nil)

resp := &http.Response{
StatusCode: http.StatusTeapot,
Request: req,
}

err := newStatusError(resp)

assert.Equal(t, "POST /some/url returned 418", err.Error())
assert.Equal(t, err, err.Data())
}
59 changes: 59 additions & 0 deletions internal/api/download.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package api

import (
"bytes"
"encoding/json"
"github.com/opg-sirius-finance-admin/internal/model"
"net/http"
)

func (c *Client) Download(ctx Context, data model.Download) error {
var body bytes.Buffer

err := json.NewEncoder(&body).Encode(data)
if err != nil {
return err
}

req, err := c.newBackendRequest(ctx, http.MethodGet, "/downloads", &body)

if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")

resp, err := c.http.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()

switch resp.StatusCode {
case http.StatusCreated:
return nil

case http.StatusUnauthorized:
return ErrUnauthorized

case http.StatusUnprocessableEntity:
var v model.ValidationError
if err := json.NewDecoder(resp.Body).Decode(&v); err == nil && len(v.Errors) > 0 {
return model.ValidationError{Errors: v.Errors}
}

case http.StatusBadRequest:
var badRequests model.BadRequests
if err := json.NewDecoder(resp.Body).Decode(&badRequests); err != nil {
return err
}

validationErrors := model.ValidationErrors{}
for _, reason := range badRequests.Reasons {
validationErrors[reason] = map[string]string{reason: reason}
}

return model.ValidationError{Errors: validationErrors}
}

return newStatusError(resp)
}
138 changes: 138 additions & 0 deletions internal/api/download_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package api

import (
"bytes"
"encoding/json"
"github.com/opg-sirius-finance-admin/internal/model"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you haven't already, get https://pkg.go.dev/golang.org/x/tools/cmd/goimports for formatting. Otherwise maybe it needs updating?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you look into this?

"github.com/stretchr/testify/assert"
"io"
"net/http"
"net/http/httptest"
"testing"
)

func TestSubmitDownload(t *testing.T) {
mockClient := &MockClient{}
client, _ := NewClient(mockClient, "http://localhost:3000", "")
dateOfTransaction := model.NewDate("2024-05-11")
dateTo := model.NewDate("2025-06-15")
dateFrom := model.NewDate("2022-07-21")

data := model.Download{
ReportType: "reportType",
ReportJournalType: "reportJournalType",
ReportScheduleType: "reportScheduleType",
ReportAccountType: "reportAccountType",
ReportDebtType: "reportDebtType",
DateOfTransaction: &dateOfTransaction,
ToDateField: &dateTo,
FromDateField: &dateFrom,
Email: "[email protected]",
}

GetDoFunc = func(*http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: http.StatusCreated,
Body: io.NopCloser(bytes.NewReader([]byte{})),
}, nil
}

err := client.Download(getContext(nil), data)
assert.NoError(t, err)
}

func TestSubmitDownloadUnauthorised(t *testing.T) {
mockClient := &MockClient{}
client, _ := NewClient(mockClient, "http://localhost:3000", "")

data := model.Download{
ReportType: "reportType",
ReportJournalType: "reportJournalType",
ReportScheduleType: "reportScheduleType",
ReportAccountType: "reportAccountType",
ReportDebtType: "reportDebtType",
DateOfTransaction: nil,
ToDateField: nil,
FromDateField: nil,
Email: "[email protected]",
}

GetDoFunc = func(*http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: http.StatusUnauthorized,
Body: io.NopCloser(bytes.NewReader([]byte{})),
}, nil
}

err := client.Download(getContext(nil), data)

assert.Equal(t, ErrUnauthorized.Error(), err.Error())
}

func TestSubmitDownloadReturnsBadRequestError(t *testing.T) {
mockClient := &MockClient{}
client, _ := NewClient(mockClient, "http://localhost:3000", "")

data := model.Download{
ReportType: "reportType",
ReportJournalType: "reportJournalType",
ReportScheduleType: "reportScheduleType",
ReportAccountType: "reportAccountType",
ReportDebtType: "reportDebtType",
DateOfTransaction: nil,
ToDateField: nil,
FromDateField: nil,
Email: "[email protected]",
}

json := `{"reasons":["StartDate","EndDate"]}`

r := io.NopCloser(bytes.NewReader([]byte(json)))

GetDoFunc = func(*http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: http.StatusBadRequest,
Body: r,
}, nil
}

err := client.Download(getContext(nil), data)

expectedError := model.ValidationError{Message: "", Errors: model.ValidationErrors{"EndDate": map[string]string{"EndDate": "EndDate"}, "StartDate": map[string]string{"StartDate": "StartDate"}}}
assert.Equal(t, expectedError, err)
}

func TestSubmitDownloadReturnsValidationError(t *testing.T) {
data := model.Download{
ReportType: "",
ReportJournalType: "reportJournalType",
ReportScheduleType: "reportScheduleType",
ReportAccountType: "reportAccountType",
ReportDebtType: "reportDebtType",
DateOfTransaction: nil,
ToDateField: nil,
FromDateField: nil,
Email: "[email protected]",
}

validationErrors := model.ValidationError{
Message: "Validation failed",
Errors: map[string]map[string]string{
"ReportType": {
"required": "Please select a report type",
},
},
}
responseBody, _ := json.Marshal(validationErrors)
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusUnprocessableEntity)
_, _ = w.Write(responseBody)
}))
defer svr.Close()

client, _ := NewClient(http.DefaultClient, svr.URL, svr.URL)

err := client.Download(getContext(nil), data)
expectedError := model.ValidationError{Message: "", Errors: model.ValidationErrors{"ReportType": map[string]string{"required": "Please select a report type"}}}
assert.Equal(t, expectedError, err.(model.ValidationError))
}
80 changes: 80 additions & 0 deletions internal/model/account_type.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package model

var ReportAccountTypes = []ReportAccountType{
ReportAccountTypeAgedDebt,
ReportAccountTypeUnappliedReceipts,
ReportAccountTypeCustomerAgeingBuckets,
ReportAccountTypeARPaidInvoiceReport,
ReportAccountTypePaidInvoiceTransactionLines,
ReportAccountTypeTotalReceiptsReport,
ReportAccountTypeBadDebtWriteOffReport,
ReportAccountTypeFeeAccrual,
}

type ReportAccountType int

const (
ReportAccountTypeUnknown ReportAccountType = iota
ReportAccountTypeAgedDebt
ReportAccountTypeUnappliedReceipts
ReportAccountTypeCustomerAgeingBuckets
ReportAccountTypeARPaidInvoiceReport
ReportAccountTypePaidInvoiceTransactionLines
ReportAccountTypeTotalReceiptsReport
ReportAccountTypeBadDebtWriteOffReport
ReportAccountTypeFeeAccrual
)

func (i ReportAccountType) String() string {
return i.Key()
}

func (i ReportAccountType) Translation() string {
switch i {
case ReportAccountTypeAgedDebt:
return "Aged Debt"
case ReportAccountTypeUnappliedReceipts:
return "Unapplied Receipts"
case ReportAccountTypeCustomerAgeingBuckets:
return "Customer Ageing Buckets"
case ReportAccountTypeARPaidInvoiceReport:
return "AR Paid Invoice Report"
case ReportAccountTypePaidInvoiceTransactionLines:
return "Paid Invoice Transaction Lines"
case ReportAccountTypeTotalReceiptsReport:
return "Total Receipts Report"
case ReportAccountTypeBadDebtWriteOffReport:
return "Bad Debt Write-off Report"
case ReportAccountTypeFeeAccrual:
return "Fee Accrual"
default:
return ""
}
}

func (i ReportAccountType) Key() string {
switch i {
case ReportAccountTypeAgedDebt:
return "AgedDebt"
case ReportAccountTypeUnappliedReceipts:
return "UnappliedReceipts"
case ReportAccountTypeCustomerAgeingBuckets:
return "CustomerAgeingBuckets"
case ReportAccountTypeARPaidInvoiceReport:
return "ARPaidInvoiceReport"
case ReportAccountTypePaidInvoiceTransactionLines:
return "PaidInvoiceTransactionLines"
case ReportAccountTypeTotalReceiptsReport:
return "TotalReceiptsReport"
case ReportAccountTypeBadDebtWriteOffReport:
return "BadDebtWriteOffReport"
case ReportAccountTypeFeeAccrual:
return "FeeAccrual"
default:
return ""
}
}

func (i ReportAccountType) Valid() bool {
return i != ReportAccountTypeUnknown
}
Loading
Loading