Skip to content

Commit

Permalink
⚡️ Added GET execution by ID endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
lhbelfanti committed Oct 10, 2024
1 parent 10de77d commit 169c50f
Show file tree
Hide file tree
Showing 7 changed files with 216 additions and 21 deletions.
5 changes: 4 additions & 1 deletion cmd/api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ func main() {
resumeCriteria := criteria.MakeResume(selectCriteriaByID, selectLastDayExecutedByCriteriaID, selectExecutionsByStatuses, scrapperEnqueueCriteria)
initCriteria := criteria.MakeInit(selectExecutionsByStatuses, resumeCriteria)

selectExecutionByID := criteria.MakeSelectExecutionByID(db)

updateCriteriaExecution := criteria.MakeUpdateExecution(db)

insertCriteriaExecutionDay := criteria.MakeInsertExecutionDay(db)
Expand All @@ -79,8 +81,9 @@ func main() {
router.HandleFunc("POST /tweets/v1", tweets.InsertHandlerV1(insertTweets))
router.HandleFunc("POST /criteria/{criteria_id}/enqueue/v1", criteria.EnqueueHandlerV1(enqueueCriteria))
router.HandleFunc("POST /criteria/init/v1", criteria.InitHandlerV1(initCriteria))
router.HandleFunc("GET /criteria/executions/{execution_id}/v1", criteria.GetExecutionByIDHandlerV1(selectExecutionByID))
router.HandleFunc("PUT /criteria/executions/{execution_id}/v1", criteria.UpdateExecutionHandlerV1(updateCriteriaExecution))
router.HandleFunc("POST /criteria/executions/{execution_id}/day/v1", criteria.InsertExecutionDayHandlerV1(insertCriteriaExecutionDay))
router.HandleFunc("POST /criteria/executions/{execution_id}/day/v1", criteria.CreateExecutionDayHandlerV1(insertCriteriaExecutionDay))
log.Info(ctx, "Router initialized!")

/* --- Server --- */
Expand Down
20 changes: 11 additions & 9 deletions cmd/api/search/criteria/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ package criteria
import "errors"

const (
InvalidURLParameter string = "Invalid url parameter"
InvalidQueryParameterFormat string = "Invalid query parameter format"
InvalidRequestBody string = "Invalid request body"
FailedToEnqueueCriteria string = "Failed to execute enqueue criteria"
ExecutionWithSameCriteriaIDAlreadyEnqueued string = "An execution with the same criteria id is already enqueued"
FailedToExecuteInitCriteria string = "Failed to execute init criteria"
FailedToExecuteInsertCriteriaExecution string = "Failed to execute insert criteria execution"
FailedToExecuteUpdateCriteriaExecution string = "Failed to execute update criteria execution"
FailedToEncodeInsertCriteriaExecutionResponse string = "Failed to encode insert criteria execution response"
InvalidURLParameter string = "Invalid url parameter"
InvalidQueryParameterFormat string = "Invalid query parameter format"
InvalidRequestBody string = "Invalid request body"
FailedToEnqueueCriteria string = "Failed to execute enqueue criteria"
ExecutionWithSameCriteriaIDAlreadyEnqueued string = "An execution with the same criteria id is already enqueued"
FailedToExecuteInitCriteria string = "Failed to execute init criteria"
FailedToExecuteInsertCriteriaExecution string = "Failed to execute insert criteria execution"
FailedToExecuteUpdateCriteriaExecution string = "Failed to execute update criteria execution"
FailedToExecuteGetExecutionsByStatuses string = "Failed to execute get criteria executions by statuses"
)

var (
Expand All @@ -32,4 +32,6 @@ var (
FailedToInsertSearchCriteriaExecutionDay = errors.New("failed to insert search criteria execution day")
FailedToRetrieveLastDayExecutedDate = errors.New("failed to retrieve last day executed date")
NoExecutionDaysFoundForTheGivenCriteriaID = errors.New("no execution days found for the given criteria id")
NoExecutionFoundForTheGivenID = errors.New("no execution found for the given id")
FailedToExecuteQueryToRetrieveExecutionData = errors.New("failed to execute query to retrieve execution data")
)
32 changes: 30 additions & 2 deletions cmd/api/search/criteria/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,34 @@ func InitHandlerV1(init Init) http.HandlerFunc {
}
}

// GetExecutionByIDHandlerV1 HTTP Handler of the endpoint /criteria/executions/{execution_id}/v1
func GetExecutionByIDHandlerV1(selectExecutionByID SelectExecutionByID) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

executionIDParam := r.PathValue("execution_id")
executionID, err := strconv.Atoi(executionIDParam)
if err != nil {
log.Error(ctx, err.Error())
http.Error(w, InvalidURLParameter, http.StatusBadRequest)
return
}
ctx = log.With(ctx, log.Param("execution_id", executionIDParam))

executions, err := selectExecutionByID(ctx, executionID)
if err != nil {
log.Error(ctx, err.Error())
http.Error(w, FailedToExecuteGetExecutionsByStatuses, http.StatusInternalServerError)
return
}

log.Info(ctx, "Executions successfully obtained")
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(executions)
}
}

// UpdateExecutionHandlerV1 HTTP Handler of the endpoint /criteria/executions/{execution_id}/v1
func UpdateExecutionHandlerV1(updateExecution UpdateExecution) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
Expand Down Expand Up @@ -106,8 +134,8 @@ func UpdateExecutionHandlerV1(updateExecution UpdateExecution) http.HandlerFunc
}
}

// InsertExecutionDayHandlerV1 HTTP Handler of the endpoint /criteria/executions/{execution_id}/day/v1
func InsertExecutionDayHandlerV1(insertExecutionDay InsertExecutionDay) http.HandlerFunc {
// CreateExecutionDayHandlerV1 HTTP Handler of the endpoint /criteria/executions/{execution_id}/day/v1
func CreateExecutionDayHandlerV1(insertExecutionDay InsertExecutionDay) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

Expand Down
77 changes: 69 additions & 8 deletions cmd/api/search/criteria/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"context"
"encoding/json"
"errors"
"io"
"net/http"
"net/http/httptest"
"testing"
Expand Down Expand Up @@ -133,6 +134,66 @@ func TestInitHandlerV1_failsWhenInitThrowsError(t *testing.T) {
assert.Equal(t, want, got)
}

func TestGetExecutionByIDHandlerV1_success(t *testing.T) {
mockExecutionDAO := criteria.MockExecutionDAO()
mockSelectExecutionByID := criteria.MockSelectExecutionByID(mockExecutionDAO, nil)
mockResponseWriter := httptest.NewRecorder()
mockRequest, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/criteria/executions/{execution_id}/v1", http.NoBody)
mockRequest.SetPathValue("execution_id", "1")

handlerV1 := criteria.GetExecutionByIDHandlerV1(mockSelectExecutionByID)

handlerV1(mockResponseWriter, mockRequest)

body, err := io.ReadAll(mockResponseWriter.Result().Body)
if err != nil {
t.Fatalf("Failed to read response body: %v", err)
}

want := mockExecutionDAO
var got criteria.ExecutionDAO
err = json.Unmarshal(body, &got)
if err != nil {
t.Fatalf("Failed to parse response body as JSON: %v", err)
}

assert.Equal(t, want, got)
assert.Equal(t, "application/json", mockResponseWriter.Header().Get("Content-Type"))
assert.Equal(t, http.StatusOK, mockResponseWriter.Result().StatusCode)
}

func TestGetExecutionByIDHandlerV1_failsWhenTheURLParamIsEmpty(t *testing.T) {
mockExecutionDAO := criteria.MockExecutionDAO()
mockSelectExecutionByID := criteria.MockSelectExecutionByID(mockExecutionDAO, nil)
mockResponseWriter := httptest.NewRecorder()
mockRequest, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/criteria/executions/{execution_id}/v1", http.NoBody)

handlerV1 := criteria.GetExecutionByIDHandlerV1(mockSelectExecutionByID)

handlerV1(mockResponseWriter, mockRequest)

want := http.StatusBadRequest
got := mockResponseWriter.Result().StatusCode

assert.Equal(t, want, got)
}

func TestGetExecutionByIDHandlerV1_failsWhenSelectExecutionByIDThrowsError(t *testing.T) {
mockSelectExecutionByID := criteria.MockSelectExecutionByID(criteria.ExecutionDAO{}, errors.New("failed to select execution by id"))
mockResponseWriter := httptest.NewRecorder()
mockRequest, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/criteria/executions/{execution_id}/v1", http.NoBody)
mockRequest.SetPathValue("execution_id", "1")

handlerV1 := criteria.GetExecutionByIDHandlerV1(mockSelectExecutionByID)

handlerV1(mockResponseWriter, mockRequest)

want := http.StatusInternalServerError
got := mockResponseWriter.Result().StatusCode

assert.Equal(t, want, got)
}

func TestUpdateExecutionHandlerV1_success(t *testing.T) {
mockUpdateExecution := criteria.MockUpdateExecution(nil)
mockResponseWriter := httptest.NewRecorder()
Expand Down Expand Up @@ -203,15 +264,15 @@ func TestUpdateExecutionHandlerV1_failsWhenUpdateExecutionThrowsError(t *testing
assert.Equal(t, want, got)
}

func TestInsertExecutionDayHandlerV1_success(t *testing.T) {
func TestCreateExecutionDayHandlerV1_success(t *testing.T) {
mockInsertExecutionDay := criteria.MockInsertExecutionDay(nil)
mockResponseWriter := httptest.NewRecorder()
mockExecutionDay := criteria.MockExecutionDayDTO(nil)
mockBody, _ := json.Marshal(mockExecutionDay)
mockRequest, _ := http.NewRequestWithContext(context.Background(), http.MethodPost, "/criteria/executions/{execution_id}/day/v1", bytes.NewReader(mockBody))
mockRequest.SetPathValue("execution_id", "1")

handlerV1 := criteria.InsertExecutionDayHandlerV1(mockInsertExecutionDay)
handlerV1 := criteria.CreateExecutionDayHandlerV1(mockInsertExecutionDay)

handlerV1(mockResponseWriter, mockRequest)

Expand All @@ -221,14 +282,14 @@ func TestInsertExecutionDayHandlerV1_success(t *testing.T) {
assert.Equal(t, want, got)
}

func TestInsertExecutionDayHandlerV1_failsWhenTheURLParamIsEmpty(t *testing.T) {
func TestCreateExecutionDayHandlerV1_failsWhenTheURLParamIsEmpty(t *testing.T) {
mockInsertExecutionDay := criteria.MockInsertExecutionDay(nil)
mockResponseWriter := httptest.NewRecorder()
mockExecutionDay := criteria.MockExecutionDayDTO(nil)
mockBody, _ := json.Marshal(mockExecutionDay)
mockRequest, _ := http.NewRequestWithContext(context.Background(), http.MethodPost, "/criteria/executions/{execution_id}/day/v1", bytes.NewReader(mockBody))

handlerV1 := criteria.InsertExecutionDayHandlerV1(mockInsertExecutionDay)
handlerV1 := criteria.CreateExecutionDayHandlerV1(mockInsertExecutionDay)

handlerV1(mockResponseWriter, mockRequest)

Expand All @@ -238,14 +299,14 @@ func TestInsertExecutionDayHandlerV1_failsWhenTheURLParamIsEmpty(t *testing.T) {
assert.Equal(t, want, got)
}

func TestInsertExecutionDayHandlerV1_failsWhenTheBodyCantBeParsed(t *testing.T) {
func TestCreateExecutionDayHandlerV1_failsWhenTheBodyCantBeParsed(t *testing.T) {
mockInsertExecutionDay := criteria.MockInsertExecutionDay(nil)
mockResponseWriter := httptest.NewRecorder()
mockBody, _ := json.Marshal(`{"wrong": "body"}`)
mockRequest, _ := http.NewRequestWithContext(context.Background(), http.MethodPost, "/criteria/executions/{execution_id}/day/v1", bytes.NewReader(mockBody))
mockRequest.SetPathValue("execution_id", "1")

handlerV1 := criteria.InsertExecutionDayHandlerV1(mockInsertExecutionDay)
handlerV1 := criteria.CreateExecutionDayHandlerV1(mockInsertExecutionDay)

handlerV1(mockResponseWriter, mockRequest)

Expand All @@ -255,15 +316,15 @@ func TestInsertExecutionDayHandlerV1_failsWhenTheBodyCantBeParsed(t *testing.T)
assert.Equal(t, want, got)
}

func TestInsertExecutionDayHandlerV1_failsWhenInsertExecutionDayThrowsError(t *testing.T) {
func TestCreateExecutionDayHandlerV1_failsWhenInsertExecutionDayThrowsError(t *testing.T) {
mockInsertExecutionDay := criteria.MockInsertExecutionDay(errors.New("failed to insert execution day"))
mockResponseWriter := httptest.NewRecorder()
mockExecutionDay := criteria.MockExecutionDayDTO(nil)
mockBody, _ := json.Marshal(mockExecutionDay)
mockRequest, _ := http.NewRequestWithContext(context.Background(), http.MethodPost, "/criteria/executions/{execution_id}/day/v1", bytes.NewReader(mockBody))
mockRequest.SetPathValue("execution_id", "1")

handlerV1 := criteria.InsertExecutionDayHandlerV1(mockInsertExecutionDay)
handlerV1 := criteria.CreateExecutionDayHandlerV1(mockInsertExecutionDay)

handlerV1(mockResponseWriter, mockRequest)

Expand Down
25 changes: 25 additions & 0 deletions cmd/api/search/criteria/mocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ func MockInit(err error) Init {
}
}

// MockSelectExecutionByID mocks SelectExecutionByID function
func MockSelectExecutionByID(executionDAO ExecutionDAO, err error) SelectExecutionByID {
return func(ctx context.Context, id int) (ExecutionDAO, error) {
return executionDAO, err
}
}

// MockInsertExecution mocks InsertExecution function
func MockInsertExecution(criteriaID int, err error) InsertExecution {
return func(ctx context.Context, searchCriteriaID int, forced bool) (int, error) {
Expand Down Expand Up @@ -130,6 +137,24 @@ func MockCriteriaDAOSlice() []DAO {
}
}

// MockExecutionDAOValues mocks the properties of ExecutionDAO to be used in the Scan function
func MockExecutionDAOValues(dao ExecutionDAO) []any {
return []any{
dao.ID,
dao.Status,
dao.SearchCriteriaID,
}
}

// MockExecutionDAO mocks an ExecutionDAO
func MockExecutionDAO() ExecutionDAO {
return ExecutionDAO{
ID: 1,
Status: DoneStatus,
SearchCriteriaID: 2,
}
}

// MockExecutionsDAO mocks a slice of ExecutionDAO
func MockExecutionsDAO() []ExecutionDAO {
return []ExecutionDAO{
Expand Down
33 changes: 32 additions & 1 deletion cmd/api/search/criteria/select.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import (
"context"
"errors"
"fmt"
"github.com/jackc/pgx/v5"
"strings"

"github.com/jackc/pgx/v5"

"ahbcc/internal/database"
"ahbcc/internal/log"
)
Expand All @@ -18,6 +19,9 @@ type (
// SelectAll returns all the criteria of the 'search_criteria' table
SelectAll func(ctx context.Context) ([]DAO, error)

// SelectExecutionByID returns an execution seeking by its ID
SelectExecutionByID func(ctx context.Context, id int) (ExecutionDAO, error)

// SelectExecutionsByStatuses returns all the search criteria executions in certain state
SelectExecutionsByStatuses func(ctx context.Context, statuses []string) ([]ExecutionDAO, error)

Expand Down Expand Up @@ -83,6 +87,33 @@ func MakeSelectAll(db database.Connection, collectRows database.CollectRows[DAO]
}
}

// MakeSelectExecutionByID creates a new SelectExecutionByID
func MakeSelectExecutionByID(db database.Connection) SelectExecutionByID {
const query string = `
SELECT id, status, search_criteria_id
FROM search_criteria_executions
WHERE id = $1
`

return func(ctx context.Context, id int) (ExecutionDAO, error) {
var execution ExecutionDAO
err := db.QueryRow(ctx, query, id).Scan(
&execution.ID,
&execution.Status,
&execution.SearchCriteriaID,
)
if errors.Is(err, pgx.ErrNoRows) {
log.Error(ctx, err.Error())
return ExecutionDAO{}, NoExecutionFoundForTheGivenID
} else if err != nil {
log.Error(ctx, err.Error())
return ExecutionDAO{}, FailedToExecuteQueryToRetrieveExecutionData
}

return execution, nil
}
}

// MakeSelectExecutionsByStatuses creates a new SelectExecutionsByStatuses
func MakeSelectExecutionsByStatuses(db database.Connection, collectRows database.CollectRows[ExecutionDAO]) SelectExecutionsByStatuses {
const query string = `
Expand Down
45 changes: 45 additions & 0 deletions cmd/api/search/criteria/select_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,51 @@ func TestSelectAll_failsWhenCollectRowsThrowsError(t *testing.T) {
mockPgxRows.AssertExpectations(t)
}

func TestSelectExecutionByID_success(t *testing.T) {
mockPostgresConnection := new(database.MockPostgresConnection)
mockPgxRow := new(database.MockPgxRow)
mockExecution := criteria.MockExecutionDAO()
mockScanCriteriaDAOValues := criteria.MockExecutionDAOValues(mockExecution)
database.MockScan(mockPgxRow, mockScanCriteriaDAOValues, t)
mockPostgresConnection.On("QueryRow", mock.Anything, mock.Anything, mock.Anything).Return(mockPgxRow)

selectExecutionByID := criteria.MakeSelectExecutionByID(mockPostgresConnection)

want := mockExecution
got, err := selectExecutionByID(context.Background(), 1)

assert.Nil(t, err)
assert.Equal(t, want, got)
mockPostgresConnection.AssertExpectations(t)
mockPgxRow.AssertExpectations(t)
}

func TestSelectExecutionByID_failsWhenSelectOperationFails(t *testing.T) {
tests := []struct {
err error
expected error
}{
{err: pgx.ErrNoRows, expected: criteria.NoExecutionFoundForTheGivenID},
{err: errors.New("failed to execute select operation"), expected: criteria.FailedToExecuteQueryToRetrieveExecutionData},
}

for _, tt := range tests {
mockPostgresConnection := new(database.MockPostgresConnection)
mockPgxRow := new(database.MockPgxRow)
mockPgxRow.On("Scan", mock.Anything).Return(tt.err)
mockPostgresConnection.On("QueryRow", mock.Anything, mock.Anything, mock.Anything).Return(mockPgxRow)

selectExecutionByID := criteria.MakeSelectExecutionByID(mockPostgresConnection)

want := tt.expected
_, got := selectExecutionByID(context.Background(), 1)

assert.Equal(t, want, got)
mockPostgresConnection.AssertExpectations(t)
mockPgxRow.AssertExpectations(t)
}
}

func TestSelectExecutionsByState_success(t *testing.T) {
mockPostgresConnection := new(database.MockPostgresConnection)
mockPgxRows := new(database.MockPgxRows)
Expand Down

0 comments on commit 169c50f

Please sign in to comment.