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

feat: add create, update, delete test #75

Merged
merged 13 commits into from
Nov 13, 2024
Merged
37 changes: 37 additions & 0 deletions apps/execution-service/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,43 @@ The following json format will be returned:
]
```

`POST /tests`

To create a new test case, run the following command:

```bash
curl -X POST http://localhost:8083/tests \
-H "Content-Type: application/json" \
-d '{
"questionDocRefId": "sampleDocRefId123",
"questionTitle": "Sample Question Title",
"visibleTestCases": "2\nhello\nolleh\nHannah\nhannaH",
"hiddenTestCases": "2\nHannah\nhannaH\nabcdefg\ngfedcba"
}'
```

`PUT /tests/{questionDocRefId}`

To update an existing test case from an existing question, run the following command:

```bash
curl -X PUT http://localhost:8083/tests/{questionDocRefId} \
-H "Content-Type: application/json" \
-d '{
"visibleTestCases": "2\nhello\nolleh\nHannah\nhannaH",
"hiddenTestCases": "2\nHannah\nhannaH\nabcdefg\ngfedcba"
}'
```

`DELETE /tests/{questionDocRefId}`

To delete an existing test case from an existing question, run the following command:

```bash
curl -X DELETE http://localhost:8083/tests/{questionDocRefId} \
-H "Content-Type: application/json"
```

`POST /tests/{questionDocRefId}/execute`

To execute test cases via a question ID without custom test cases, run the following command, with custom code and language:
Expand Down
2 changes: 1 addition & 1 deletion apps/execution-service/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
github.com/go-chi/chi/v5 v5.1.0
github.com/go-chi/cors v1.2.1
github.com/joho/godotenv v1.5.1
github.com/rabbitmq/amqp091-go v1.10.0
github.com/traefik/yaegi v0.16.1
google.golang.org/api v0.203.0
)
Expand All @@ -31,7 +32,6 @@ require (
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
github.com/googleapis/gax-go/v2 v2.13.0 // indirect
github.com/rabbitmq/amqp091-go v1.10.0 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions apps/execution-service/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHy
go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok=
go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
Expand Down
119 changes: 119 additions & 0 deletions apps/execution-service/handlers/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package handlers

import (
"cloud.google.com/go/firestore"
"encoding/json"
"execution-service/models"
"execution-service/utils"
"google.golang.org/api/iterator"
"net/http"
)

func (s *Service) CreateTest(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

var test models.Test
if err := utils.DecodeJSONBody(w, r, &test); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

// Basic validation for question title and question ID
if test.QuestionDocRefId == "" || test.QuestionTitle == "" {
http.Error(w, "QuestionDocRefId and QuestionTitle are required", http.StatusBadRequest)
return
}

// Normalise test cases
test.VisibleTestCases = utils.NormaliseTestCaseFormat(test.VisibleTestCases)
test.HiddenTestCases = utils.NormaliseTestCaseFormat(test.HiddenTestCases)

// Automatically populate validation for input and output in test case
test.InputValidation = utils.GetDefaultValidation()
test.OutputValidation = utils.GetDefaultValidation()

// Validate test case format
if _, err := utils.ValidateTestCaseFormat(test.VisibleTestCases, test.InputValidation,
test.OutputValidation); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if _, err := utils.ValidateTestCaseFormat(test.HiddenTestCases, test.InputValidation,
test.OutputValidation); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

// Check if a test already exists for the question
iter := s.Client.Collection("tests").Where("questionDocRefId", "==", test.QuestionDocRefId).Documents(ctx)
for {
_, err := iter.Next()
if err == iterator.Done {
break
}
if err != nil {
http.Error(w, "Error fetching test", http.StatusInternalServerError)
return
}
http.Error(w, "Test already exists for the question", http.StatusConflict)
return
}
defer iter.Stop()

// Save test to Firestore
docRef, _, err := s.Client.Collection("tests").Add(ctx, map[string]interface{}{
"questionDocRefId": test.QuestionDocRefId,
"questionTitle": test.QuestionTitle,
"visibleTestCases": test.VisibleTestCases,
"hiddenTestCases": test.HiddenTestCases,
"inputValidation": test.InputValidation,
"outputValidation": test.OutputValidation,
"createdAt": firestore.ServerTimestamp,
"updatedAt": firestore.ServerTimestamp,
})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

// Get data
doc, err := docRef.Get(ctx)
if err != nil {
if err != iterator.Done {
http.Error(w, "Test not found", http.StatusInternalServerError)
return
}
http.Error(w, "Failed to get test", http.StatusInternalServerError)
return
}

// Map data
if err := doc.DataTo(&test); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(test)
}

// Manual test cases

//curl -X POST http://localhost:8083/tests \
//-H "Content-Type: application/json" \
//-d '{
//"questionDocRefId": "sampleDocRefId123",
//"questionTitle": "Sample Question Title",
//"visibleTestCases": "2\nhello\nolleh\nHannah\nhannaH",
//"hiddenTestCases": "2\nHannah\nhannaH\nabcdefg\ngfedcba"
//}'

//curl -X POST http://localhost:8083/tests \
//-H "Content-Type: application/json" \
//-d "{
//\"questionDocRefId\": \"sampleDocRefId12345\",
//\"questionTitle\": \"Sample Question Title\",
//\"visibleTestCases\": \"2\\nhello\\nolleh\\nHannah\\nhannaH\",
//\"hiddenTestCases\": \"2\\nHannah\\nhannaH\\nabcdefg\\ngfedcba\"
//}"
40 changes: 40 additions & 0 deletions apps/execution-service/handlers/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package handlers

import (
"github.com/go-chi/chi/v5"
"google.golang.org/api/iterator"
"net/http"
)

func (s *Service) DeleteTest(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

// Parse request
docRefID := chi.URLParam(r, "questionDocRefId")

docRef := s.Client.Collection("tests").Where("questionDocRefId", "==", docRefID).Limit(1).Documents(ctx)
doc, err := docRef.Next()
if err != nil {
if err == iterator.Done {
http.Error(w, "Test not found", http.StatusNotFound)
return
}
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer docRef.Stop()

_, err = doc.Ref.Delete(ctx)
if err != nil {
http.Error(w, "Error deleting test", http.StatusInternalServerError)
return
}

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
}

// Manual test cases

//curl -X DELETE http://localhost:8083/tests/sampleDocRefId123 \
//-H "Content-Type: application/json"
71 changes: 71 additions & 0 deletions apps/execution-service/handlers/readall.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package handlers

import (
"encoding/json"
"execution-service/models"
"execution-service/utils"
"net/http"

"github.com/go-chi/chi/v5"
"google.golang.org/api/iterator"
)

func (s *Service) ReadAllTests(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

questionDocRefId := chi.URLParam(r, "questionDocRefId")
if questionDocRefId == "" {
http.Error(w, "questionDocRefId is required", http.StatusBadRequest)
return
}

iter := s.Client.Collection("tests").Where("questionDocRefId", "==", questionDocRefId).Limit(1).Documents(ctx)
doc, err := iter.Next()
if err != nil {
if err == iterator.Done {
http.Error(w, "Test not found", http.StatusNotFound)
return
}
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer iter.Stop()

var test models.Test
if err := doc.DataTo(&test); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

_, hiddenTestCases, err := utils.GetTestLengthAndUnexecutedCases(test.HiddenTestCases)

var hiddenTests []models.HiddenTest
for _, hiddenTestCase := range hiddenTestCases {
hiddenTests = append(hiddenTests, models.HiddenTest{
Input: hiddenTestCase.Input,
Expected: hiddenTestCase.Expected,
})
}

_, visibleTestCases, err := utils.GetTestLengthAndUnexecutedCases(test.VisibleTestCases)

var visibleTests []models.VisibleTest
for _, visibleTestCase := range visibleTestCases {
visibleTests = append(visibleTests, models.VisibleTest{
Input: visibleTestCase.Input,
Expected: visibleTestCase.Expected,
})
}

allTests := models.AllTests{
VisibleTests: visibleTests,
HiddenTests: hiddenTests,
}

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(allTests)
}

//curl -X GET http://localhost:8083/tests/bmzFyLMeSOoYU99pi4yZ/ \
//-H "Content-Type: application/json"
96 changes: 96 additions & 0 deletions apps/execution-service/handlers/update.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package handlers

import (
"cloud.google.com/go/firestore"
"encoding/json"
"execution-service/models"
"execution-service/utils"
"github.com/go-chi/chi/v5"
"google.golang.org/api/iterator"
"net/http"
)

func (s *Service) UpdateTest(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

// get param questionDocRefId
questionDocRefId := chi.URLParam(r, "questionDocRefId")

var test models.Test
if err := utils.DecodeJSONBody(w, r, &test); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

// Normalise test cases
test.VisibleTestCases = utils.NormaliseTestCaseFormat(test.VisibleTestCases)
test.HiddenTestCases = utils.NormaliseTestCaseFormat(test.HiddenTestCases)

// Only test cases will be updated
// Validate test case format with default validation
if _, err := utils.ValidateTestCaseFormat(test.VisibleTestCases, utils.GetDefaultValidation(),
utils.GetDefaultValidation()); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if _, err := utils.ValidateTestCaseFormat(test.HiddenTestCases, utils.GetDefaultValidation(),
utils.GetDefaultValidation()); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

// Update test in Firestore
docRef := s.Client.Collection("tests").Where("questionDocRefId", "==", questionDocRefId).Limit(1).Documents(ctx)
doc, err := docRef.Next()
if err != nil {
if err == iterator.Done {
http.Error(w, "Test not found", http.StatusNotFound)
return
}
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer docRef.Stop()

// Update database
updates := []firestore.Update{
{Path: "visibleTestCases", Value: test.VisibleTestCases},
{Path: "hiddenTestCases", Value: test.HiddenTestCases},
{Path: "updatedAt", Value: firestore.ServerTimestamp},
}
_, err = doc.Ref.Update(ctx, updates)
if err != nil {
http.Error(w, "Error updating test", http.StatusInternalServerError)
return
}

// Get data
doc, err = doc.Ref.Get(ctx)
if err != nil {
if err != iterator.Done {
http.Error(w, "Test not found", http.StatusNotFound)
return
}
http.Error(w, "Failed to get test", http.StatusInternalServerError)
return
}

// Map data
if err = doc.DataTo(&test); err != nil {
http.Error(w, "Failed to map test data", http.StatusInternalServerError)
return
}

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(test)
}

// Manual test cases

//curl -X PUT http://localhost:8083/tests/sampleDocRefId123 \
//-H "Content-Type: application/json" \
//-d '{
//"visibleTestCases": "2\nhello\nolleh\nHannah\nhannaH",
//"hiddenTestCases": "2\nHannah\nhannaH\nabcdefg\ngfedcba"
//}'
Loading