Skip to content

Commit

Permalink
added API basic tests
Browse files Browse the repository at this point in the history
  • Loading branch information
anthdm committed Jan 11, 2024
1 parent b1a98bc commit 5ea0626
Show file tree
Hide file tree
Showing 9 changed files with 224 additions and 27 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ api: build

test:
@./internal/_testdata/build.sh
@go test ./internal/* -v
@go test ./internal/*

proto:
protoc --go_out=. --go_opt=paths=source_relative --proto_path=$(PROTO_PATH) --proto_path=. proto/types.proto
Expand Down
7 changes: 4 additions & 3 deletions cmd/cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,23 @@ import (
"github.com/anthdm/raptor/internal/client"
"github.com/anthdm/raptor/internal/config"
"github.com/anthdm/raptor/internal/types"
"github.com/anthdm/raptor/internal/version"
"github.com/google/uuid"
)

func printUsage() {
fmt.Printf(`
Raptor cli v0.0.1
Raptor cli v%s
Usage: raptor COMMAND
Commands:
endpoint Create a new endpoint
publish Publish a specific deployment to your applications endpoint
publish Publish a deployment to an endpoint
deploy Create a new deployment
help Show usage
`)
`, version.Version)
os.Exit(0)
}

Expand Down
2 changes: 1 addition & 1 deletion examples/go/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@ func main() {
router := chi.NewMux()
router.Get("/dashboard", handleDashboard)
router.Get("/login", handleLogin)
raptor.Handle(http.HandlerFunc(handleLogin))
raptor.Handle(router)
}
38 changes: 21 additions & 17 deletions internal/api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func (s *Server) initRouter() {
s.router.Get("/endpoint/{id}/metrics", makeAPIHandler(s.handleGetEndpointMetrics))
s.router.Post("/endpoint", makeAPIHandler(s.handleCreateEndpoint))
s.router.Post("/endpoint/{id}/deployment", makeAPIHandler(s.handleCreateDeployment))
s.router.Post("/publish/{id}", makeAPIHandler(s.handlePublish))
s.router.Post("/publish", makeAPIHandler(s.handlePublish))
}

func handleStatus(w http.ResponseWriter, r *http.Request) {
Expand Down Expand Up @@ -115,9 +115,16 @@ func (s *Server) handleCreateDeployment(w http.ResponseWriter, r *http.Request)
return writeJSON(w, http.StatusNotFound, ErrorResponse(err))
}

// TODO:
// 1. validate the contents of the blob.
// 2. make sure we have a limit on the maximum blob size.
b, err := io.ReadAll(r.Body)
if err != nil {
return writeJSON(w, http.StatusNotFound, ErrorResponse(err))
return writeJSON(w, http.StatusBadRequest, ErrorResponse(err))
}
if len(b) == 0 {
err := fmt.Errorf("no blob")
return writeJSON(w, http.StatusBadRequest, ErrorResponse(err))
}
deploy := types.NewDeployment(endpoint, b)
if err := s.store.CreateDeployment(deploy); err != nil {
Expand All @@ -139,11 +146,12 @@ func (s *Server) handleGetEndpoint(w http.ResponseWriter, r *http.Request) error
}

func (s *Server) handleGetEndpoints(w http.ResponseWriter, r *http.Request) error {
endpoints, err := s.store.GetEndpoints()
if err != nil {
return writeJSON(w, http.StatusNotFound, ErrorResponse(err))
}
return writeJSON(w, http.StatusOK, endpoints)
return nil
// endpoints, err := s.store.GetEndpoints()
// if err != nil {
// return writeJSON(w, http.StatusNotFound, ErrorResponse(err))
// }
// return writeJSON(w, http.StatusOK, endpoints)
}

// PublishParams holds all the necessary fields to publish a specific
Expand All @@ -158,11 +166,12 @@ type PublishResponse struct {
}

func (s *Server) handlePublish(w http.ResponseWriter, r *http.Request) error {
deployID, err := uuid.Parse(chi.URLParam(r, "id"))
if err != nil {
var params PublishParams
if err := json.NewDecoder(r.Body).Decode(&params); err != nil {
err := fmt.Errorf("failed to parse the response body: %s", err)
return writeJSON(w, http.StatusBadRequest, ErrorResponse(err))
}
deploy, err := s.store.GetDeployment(deployID)
deploy, err := s.store.GetDeployment(params.DeploymentID)
if err != nil {
return writeJSON(w, http.StatusBadRequest, ErrorResponse(err))
}
Expand All @@ -173,13 +182,8 @@ func (s *Server) handlePublish(w http.ResponseWriter, r *http.Request) error {

currentDeploymentID := endpoint.ActiveDeploymentID

var params PublishParams
if err := json.NewDecoder(r.Body).Decode(&params); err != nil {
return writeJSON(w, http.StatusBadRequest, ErrorResponse(err))
}

if currentDeploymentID.String() == params.DeploymentID.String() {
err := fmt.Errorf("deploy %s already active", params.DeploymentID)
if currentDeploymentID.String() == deploy.ID.String() {
err := fmt.Errorf("deploy %s already active", deploy.ID)
return writeJSON(w, http.StatusBadRequest, ErrorResponse(err))
}

Expand Down
107 changes: 105 additions & 2 deletions internal/api/server_test.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,122 @@
package api

import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"

"github.com/anthdm/raptor/internal/shared"
"github.com/anthdm/raptor/internal/storage"
"github.com/anthdm/raptor/internal/types"
"github.com/stretchr/testify/require"
)

func TestCreateEndpoint(t *testing.T) {
s := createServer()

name := "My endpoint"
runtime := "go"
environment := map[string]string{"FOO": "BAR"}

params := CreateEndpointParams{
Name: name,
Runtime: runtime,
Environment: environment,
}
b, err := json.Marshal(params)
require.Nil(t, err)

req := httptest.NewRequest("POST", "/endpoint", bytes.NewReader(b))
resp := httptest.NewRecorder()
s.router.ServeHTTP(resp, req)

var endpoint types.Endpoint
err = json.NewDecoder(resp.Body).Decode(&endpoint)
require.Nil(t, err)

require.Equal(t, http.StatusOK, resp.Result().StatusCode)
require.Equal(t, name, endpoint.Name)
require.Equal(t, runtime, endpoint.Runtime)
require.Equal(t, environment, endpoint.Environment)
require.True(t, shared.IsZeroUUID(endpoint.ActiveDeploymentID))
}

func TestGetEndpoint(t *testing.T) {
s := createServer()
endpoint := seedEndpoint(t, s)

req := httptest.NewRequest("GET", "/endpoint/"+endpoint.ID.String(), nil)
resp := httptest.NewRecorder()
s.router.ServeHTTP(resp, req)

require.Equal(t, http.StatusOK, resp.Result().StatusCode)

var other types.Endpoint
err := json.NewDecoder(resp.Body).Decode(&other)
require.Nil(t, err)
endpoint.CreatedAT = time.Time{}
other.CreatedAT = time.Time{}
require.Equal(t, *endpoint, other)
}

func TestCreateDeploy(t *testing.T) {
s := createServer()
endpoint := seedEndpoint(t, s)

req := httptest.NewRequest("POST", "/endpoint/"+endpoint.ID.String()+"/deployment", bytes.NewReader([]byte("a")))
req.Header.Set("content-type", "application/octet-stream")
resp := httptest.NewRecorder()
s.router.ServeHTTP(resp, req)

require.Equal(t, http.StatusOK, resp.Result().StatusCode)

var deploy types.Deployment
require.Nil(t, json.NewDecoder(resp.Body).Decode(&deploy))

require.Equal(t, endpoint.ID, deploy.EndpointID)
require.Equal(t, 32, len(deploy.Hash))
}

func TestPublish(t *testing.T) {
s := createServer()
endpoint := seedEndpoint(t, s)
deployment := types.NewDeployment(endpoint, []byte("somefakeblob"))

require.Nil(t, s.store.CreateDeployment(deployment))
require.True(t, shared.IsZeroUUID(endpoint.ActiveDeploymentID))

params := PublishParams{
DeploymentID: deployment.ID,
}
b, err := json.Marshal(params)
require.Nil(t, err)

req := httptest.NewRequest("POST", "/publish", bytes.NewReader(b))
resp := httptest.NewRecorder()
s.router.ServeHTTP(resp, req)

var publishResp PublishResponse
require.Nil(t, json.NewDecoder(resp.Body).Decode(&publishResp))

require.Equal(t, http.StatusOK, resp.Result().StatusCode)
require.Equal(t, deployment.ID, endpoint.ActiveDeploymentID)
require.Equal(t, deployment.ID, publishResp.DeploymentID)
require.Equal(t, "http://0.0.0.0:80/live/"+endpoint.ID.String(), publishResp.URL)
}

func TestGetDeploy(t *testing.T) {
func seedEndpoint(t *testing.T, s *Server) *types.Endpoint {
e := types.NewEndpoint("My endpoint", "go", map[string]string{"FOO": "BAR"})
require.Nil(t, s.store.CreateEndpoint(e))
return e
}

func TestRollback(t *testing.T) {}
func createServer() *Server {
cache := storage.NewDefaultModCache()
store := storage.NewMemoryStore()
s := NewServer(store, store, cache)
s.initRouter()
return s
}
2 changes: 1 addition & 1 deletion internal/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func (c *Client) Publish(params api.PublishParams) (*api.PublishResponse, error)
if err != nil {
return nil, err
}
url := fmt.Sprintf("%s/publish/%s", c.config.url, params.DeploymentID)
url := fmt.Sprintf("%s/publish", c.config.url)
req, err := http.NewRequest("POST", url, bytes.NewReader(b))
if err != nil {
return nil, err
Expand Down
10 changes: 9 additions & 1 deletion internal/shared/shared.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@ import (
"strings"

"github.com/anthdm/raptor/proto"
"github.com/google/uuid"
)

const magicLen = 8
const (
magicLen = 8
UUIDZERO = "00000000-0000-0000-0000-000000000000"
)

var errInvalidHTTPResponse = errors.New("invalid HTTP response")

Expand Down Expand Up @@ -99,3 +103,7 @@ func GetDecodedStdout(runtime string, stdout io.Reader) io.Reader {
return stdout
}
}

func IsZeroUUID(id uuid.UUID) bool {
return id.String() == UUIDZERO
}
82 changes: 82 additions & 0 deletions internal/storage/memory_store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package storage

import (
"fmt"
"sync"

"github.com/anthdm/raptor/internal/types"
"github.com/google/uuid"
)

type MemoryStore struct {
mu sync.RWMutex
endpoints map[uuid.UUID]*types.Endpoint
deploys map[uuid.UUID]*types.Deployment
}

func NewMemoryStore() *MemoryStore {
return &MemoryStore{
endpoints: make(map[uuid.UUID]*types.Endpoint),
deploys: make(map[uuid.UUID]*types.Deployment),
}
}

func (s *MemoryStore) CreateEndpoint(e *types.Endpoint) error {
s.mu.Lock()
defer s.mu.Unlock()
s.endpoints[e.ID] = e
return nil
}

func (s *MemoryStore) GetEndpoint(id uuid.UUID) (*types.Endpoint, error) {
s.mu.RLock()
defer s.mu.RUnlock()
e, ok := s.endpoints[id]
if !ok {
return nil, fmt.Errorf("could not find endpoint with id (%s)", id)
}
return e, nil
}

func (s *MemoryStore) UpdateEndpoint(id uuid.UUID, params UpdateEndpointParams) error {
endpoint, err := s.GetEndpoint(id)
if err != nil {
return err
}
s.mu.Lock()
defer s.mu.Unlock()
if params.ActiveDeployID.String() != "00000000-0000-0000-0000-000000000000" {
endpoint.ActiveDeploymentID = params.ActiveDeployID
}
if params.Environment != nil {
for key, val := range params.Environment {
endpoint.Environment[key] = val
}
}
return nil
}

func (s *MemoryStore) CreateDeployment(deploy *types.Deployment) error {
s.mu.Lock()
defer s.mu.Unlock()
s.deploys[deploy.ID] = deploy
return nil
}

func (s *MemoryStore) GetDeployment(id uuid.UUID) (*types.Deployment, error) {
s.mu.RLock()
defer s.mu.RUnlock()
deploy, ok := s.deploys[id]
if !ok {
return nil, fmt.Errorf("could not find deployment with id (%s)", id)
}
return deploy, nil
}

func (s *MemoryStore) CreateRuntimeMetric(_ *types.RuntimeMetric) error {
return nil
}

func (s *MemoryStore) GetRuntimeMetrics(_ uuid.UUID) ([]types.RuntimeMetric, error) {
return nil, nil
}
1 change: 0 additions & 1 deletion internal/storage/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ type Store interface {
CreateEndpoint(*types.Endpoint) error
UpdateEndpoint(uuid.UUID, UpdateEndpointParams) error
GetEndpoint(uuid.UUID) (*types.Endpoint, error)
GetEndpoints() ([]types.Endpoint, error)
CreateDeployment(*types.Deployment) error
GetDeployment(uuid.UUID) (*types.Deployment, error)
}
Expand Down

0 comments on commit 5ea0626

Please sign in to comment.