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

Add test coverage and use Problem Details in ratsd core API #29

Merged
merged 5 commits into from
Feb 20, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
BIN := ratsd

.PHONY: all
all: generate build
all: generate build test

.PHONY: gen-certs
gen-certs:
Expand All @@ -19,6 +19,10 @@ generate:
build:
go build -o $(BIN) -buildmode=pie ./cmd

.PHONY: test
test:
go test -v github.com/veraison/ratsd/api

.PHONY: clean
clean:
rm -f $(BIN)
Expand Down
63 changes: 44 additions & 19 deletions api/server.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Copyright 2025 Contributors to the Veraison project.
// SPDX-License-Identifier: Apache-2.0
package api

import (
Expand All @@ -6,12 +8,14 @@ import (
"io"
"net/http"

"github.com/moogar0880/problems"
"go.uber.org/zap"
)

// Defines missing consts in the API Spec
const (
ApplicationvndVeraisonCharesJson string = "application/vnd.veraison.chares+json"
ExpectedAuth string = "Bearer my.jwt.token"
)

type Server struct {
Expand All @@ -24,48 +28,69 @@ func NewServer(logger *zap.SugaredLogger) *Server {
}
}

func (s *Server) returnBadRequest(w http.ResponseWriter, r *http.Request, errMsg string) {
s.logger.Error(errMsg)
badRequestError := &BadRequestError{
Detail: &errMsg,
Status: N400,
Title: InvalidRequest,
Type: TagGithubCom2024VeraisonratsdErrorInvalidrequest,
}
w.WriteHeader(http.StatusBadRequest)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(badRequestError)
func (s *Server) reportProblem(w http.ResponseWriter, prob *problems.DefaultProblem) {
s.logger.Error(prob.Detail)
w.Header().Set("Content-Type", problems.ProblemMediaType)
w.WriteHeader(prob.ProblemStatus())
json.NewEncoder(w).Encode(prob)
}

func (s *Server) RatsdChares(w http.ResponseWriter, r *http.Request, param RatsdCharesParams) {
var requestData ChaResRequest

auth := r.Header.Get("Authorization")
if auth != ExpectedAuth {
p := &problems.DefaultProblem{
Type: string(TagGithubCom2024VeraisonratsdErrorUnauthorized),
Title: string(AccessUnauthorized),
Detail: "wrong or missing authorization header",
Status: http.StatusUnauthorized,
}
s.reportProblem(w, p)
return
}

// Check if content type matches the expectation
ct := r.Header.Get("Content-Type")
if ct != ApplicationvndVeraisonCharesJson {
errMsg := fmt.Sprintf("wrong content type, expect %s (got %s)", ApplicationvndVeraisonCharesJson, ct)
s.returnBadRequest(w, r, errMsg)
p := &problems.DefaultProblem{
Type: string(TagGithubCom2024VeraisonratsdErrorInvalidrequest),
Title: string(InvalidRequest),
Detail: errMsg,
Status: http.StatusBadRequest,
}
s.reportProblem(w, p)
return
}

respCt := fmt.Sprintf(`application/eat+jwt; eat_profile=%q`, TagGithubCom2024Veraisonratsd)
if *(param.Accept) != respCt {
errMsg := fmt.Sprintf("wrong accept type, expect %s (got %s)", respCt, *(param.Accept))
w.WriteHeader(http.StatusNotAcceptable)
w.Write([]byte(errMsg))
return
if param.Accept != nil {
s.logger.Info("request media type: ", *(param.Accept))
if *(param.Accept) != respCt && *(param.Accept) != "*/*" {
errMsg := fmt.Sprintf(
"wrong accept type, expect %s (got %s)", respCt, *(param.Accept))
p := problems.NewDetailedProblem(http.StatusNotAcceptable, errMsg)
s.reportProblem(w, p)
return
}
}

payload, _ := io.ReadAll(r.Body)
err := json.Unmarshal(payload, &requestData)
if err != nil || len(requestData.Nonce) < 1 {
errMsg := "fail to retrieve nonce from the request"
s.returnBadRequest(w, r, errMsg)
p := &problems.DefaultProblem{
Type: string(TagGithubCom2024VeraisonratsdErrorInvalidrequest),
Title: string(InvalidRequest),
Detail: errMsg,
Status: http.StatusBadRequest,
}
s.reportProblem(w, p)
return
}

s.logger.Info("request nonce: ", requestData.Nonce)
s.logger.Info("request media type: ", *(param.Accept))
w.Header().Set("Content-Type", respCt)
w.WriteHeader(http.StatusOK)
w.Write([]byte("hello from ratsd!"))
Expand Down
153 changes: 153 additions & 0 deletions api/server_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// Copyright 2025 Contributors to the Veraison project.
// SPDX-License-Identifier: Apache-2.0
package api

import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"

"github.com/moogar0880/problems"
"github.com/stretchr/testify/assert"
"github.com/veraison/services/log"
)

const (
jsonType = "application/json"
)

func TestRatsdChares_missing_auth_header(t *testing.T) {
expectedCode := http.StatusUnauthorized
expectedType := problems.ProblemMediaType
expectedBody := &problems.DefaultProblem{
Type: string(TagGithubCom2024VeraisonratsdErrorUnauthorized),
Title: string(AccessUnauthorized),
Status: http.StatusUnauthorized,
Detail: "wrong or missing authorization header",
}

var params RatsdCharesParams
logger := log.Named("test")
s := &Server{logger: logger}
w := httptest.NewRecorder()
r, _ := http.NewRequest(http.MethodPost, "/ratsd/chares", http.NoBody)
s.RatsdChares(w, r, params)

var body problems.DefaultProblem
_ = json.Unmarshal(w.Body.Bytes(), &body)

assert.Equal(t, expectedCode, w.Code)
assert.Equal(t, expectedType, w.Result().Header.Get("Content-Type"))
assert.Equal(t, expectedBody, &body)
}

func TestRatsdChares_wrong_content_type(t *testing.T) {
expectedCode := http.StatusBadRequest
expectedType := problems.ProblemMediaType
expectedBody := &problems.DefaultProblem{
Type: string(TagGithubCom2024VeraisonratsdErrorInvalidrequest),
Title: string(InvalidRequest),
Status: http.StatusBadRequest,
Detail: fmt.Sprintf("wrong content type, expect %s (got %s)", ApplicationvndVeraisonCharesJson, jsonType),
}

var params RatsdCharesParams
logger := log.Named("test")
s := &Server{logger: logger}
w := httptest.NewRecorder()
r, _ := http.NewRequest(http.MethodPost, "/ratsd/chares", http.NoBody)
r.Header.Add("Authorization", ExpectedAuth)
r.Header.Add("Content-Type", jsonType)
s.RatsdChares(w, r, params)

var body problems.DefaultProblem
_ = json.Unmarshal(w.Body.Bytes(), &body)

assert.Equal(t, expectedCode, w.Code)
assert.Equal(t, expectedType, w.Result().Header.Get("Content-Type"))
assert.Equal(t, expectedBody, &body)
}

func TestRatsdChares_wrong_accept_type(t *testing.T) {
var params RatsdCharesParams

param := jsonType
params.Accept = &param
logger := log.Named("test")
s := &Server{logger: logger}
w := httptest.NewRecorder()
r, _ := http.NewRequest(http.MethodPost, "/ratsd/chares", http.NoBody)
r.Header.Add("Authorization", ExpectedAuth)
r.Header.Add("Content-Type", ApplicationvndVeraisonCharesJson)
s.RatsdChares(w, r, params)

respCt := fmt.Sprintf(`application/eat+jwt; eat_profile=%q`, TagGithubCom2024Veraisonratsd)
expectedCode := http.StatusNotAcceptable
expectedType := problems.ProblemMediaType
expectedDetail := fmt.Sprintf("wrong accept type, expect %s (got %s)", respCt, *(params.Accept))
expectedBody := problems.NewDetailedProblem(http.StatusNotAcceptable, expectedDetail)

var body problems.DefaultProblem
_ = json.Unmarshal(w.Body.Bytes(), &body)

assert.Equal(t, expectedCode, w.Code)
assert.Equal(t, expectedType, w.Result().Header.Get("Content-Type"))
assert.Equal(t, expectedBody, &body)
}

func TestRatsdChares_missing_nonce(t *testing.T) {
var params RatsdCharesParams

param := fmt.Sprintf(`application/eat+jwt; eat_profile=%q`, TagGithubCom2024Veraisonratsd)
params.Accept = &param
logger := log.Named("test")
s := &Server{logger: logger}
w := httptest.NewRecorder()
rb := strings.NewReader("{\"noncee\": \"MIDBNH28iioisjPy\"}")
r, _ := http.NewRequest(http.MethodPost, "/ratsd/chares", rb)
r.Header.Add("Authorization", ExpectedAuth)
r.Header.Add("Content-Type", ApplicationvndVeraisonCharesJson)
s.RatsdChares(w, r, params)

expectedCode := http.StatusBadRequest
expectedType := problems.ProblemMediaType
expectedBody := &problems.DefaultProblem{
Type: string(TagGithubCom2024VeraisonratsdErrorInvalidrequest),
Title: string(InvalidRequest),
Status: http.StatusBadRequest,
Detail: "fail to retrieve nonce from the request",
}

var body problems.DefaultProblem
_ = json.Unmarshal(w.Body.Bytes(), &body)

assert.Equal(t, expectedCode, w.Code)
assert.Equal(t, expectedType, w.Result().Header.Get("Content-Type"))
assert.Equal(t, expectedBody, &body)
}

func TestRatsdChares_valid_request(t *testing.T) {
var params RatsdCharesParams

param := fmt.Sprintf(`application/eat+jwt; eat_profile=%q`, TagGithubCom2024Veraisonratsd)
params.Accept = &param
logger := log.Named("test")
s := &Server{logger: logger}
w := httptest.NewRecorder()
rb := strings.NewReader("{\"nonce\": \"MIDBNH28iioisjPy\"}")
r, _ := http.NewRequest(http.MethodPost, "/ratsd/chares", rb)
r.Header.Add("Authorization", ExpectedAuth)
r.Header.Add("Content-Type", ApplicationvndVeraisonCharesJson)
s.RatsdChares(w, r, params)

expectedCode := http.StatusOK
expectedType := param
expectedBody := "hello from ratsd!"

assert.Equal(t, expectedCode, w.Code)
assert.Equal(t, expectedType, w.Result().Header.Get("Content-Type"))
assert.Equal(t, expectedBody, w.Body.String())
}
2 changes: 2 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Copyright 2025 Contributors to the Veraison project.
// SPDX-License-Identifier: Apache-2.0
package main

import (
Expand Down
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ go 1.22.7

require (
github.com/getkin/kin-openapi v0.128.0
github.com/moogar0880/problems v0.1.1
github.com/oapi-codegen/runtime v1.1.1
github.com/stretchr/testify v1.9.0
github.com/veraison/services v0.0.2501
go.uber.org/zap v1.23.0
google.golang.org/grpc v1.64.0
Expand All @@ -17,6 +19,7 @@ require (
github.com/bytedance/sonic v1.11.3 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
github.com/chenzhuoyu/iasm v0.9.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
Expand Down Expand Up @@ -45,12 +48,12 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/moogar0880/problems v0.1.1 // indirect
github.com/oapi-codegen/oapi-codegen/v2 v2.4.1 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.2.0 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/petar-dambovaliev/aho-corasick v0.0.0-20211021192214-5ab2d9280aa9 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/speakeasy-api/openapi-overlay v0.9.0 // indirect
github.com/spf13/afero v1.9.2 // indirect
github.com/spf13/cast v1.5.0 // indirect
Expand Down