Skip to content

Commit

Permalink
feat(PL-2554): add e2e test
Browse files Browse the repository at this point in the history
  • Loading branch information
davidmdm committed Mar 21, 2024
1 parent a1fd069 commit 9eb1906
Show file tree
Hide file tree
Showing 9 changed files with 226 additions and 85 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,7 @@ tmp
go.work

*.pem

*.env

test_output
95 changes: 93 additions & 2 deletions cmd/server/api_get_params_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,97 @@
package main

import "testing"
import (
"bytes"
"cmp"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"

func TestGetParams(t *testing.T) {
"github.com/rs/zerolog"
"github.com/stretchr/testify/require"

"github.com/nestoca/joy-generator/internal/generator"
"github.com/nestoca/joy-generator/internal/github"
)

func TestGetParamsE2E(t *testing.T) {
logs := &TestLogOutputs{}

logger := zerolog.New(logs)

user := github.User{
Name: os.Getenv("GH_USER"),
Token: os.Getenv("GH_TOKEN"),
}

catalog := github.RepoMetadata{
Path: cmp.Or(os.Getenv("CATALOG_PATH"), filepath.Join(os.TempDir(), "catalog-test")),
URL: os.Getenv("CATALOG_URL"),
TargetRevision: os.Getenv("CATALOG_REVISION"),
}

require.NoError(t, os.RemoveAll(catalog.Path))

repo, err := user.NewRepo(catalog)
require.NoError(t, err)

repo = repo.WithLogger(logger)

handler := Handler(HandlerParams{
pluginToken: "test-token",
logger: logger,
repo: repo,
generator: &generator.Generator{
LoadJoyContext: generator.RepoLoader(repo),
Logger: logger,
},
})

server := httptest.NewServer(handler)
defer server.Close()

req, err := http.NewRequest("GET", server.URL+"/api/v1/getparams.execute", strings.NewReader("{}"))
require.NoError(t, err)

req.Header.Set("Authorization", "Bearer test-token")

resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
defer resp.Body.Close()

var body bytes.Buffer
_, err = io.Copy(&body, resp.Body)
require.NoError(t, err)

require.Equal(t, 200, resp.StatusCode, body.String())

var response generator.GetParamsResponse
require.NoError(t, json.Unmarshal(body.Bytes(), &response))

require.Greater(t, len(response.Output.Parameters), 0)

require.Greater(t, len(logs.Records), 0)
for _, record := range logs.Records {
require.NotEmpty(t, record["level"])
require.NotEqual(t, "error", record["level"])
}
}

type TestLogOutputs struct {
Records []map[string]any
}

func (output *TestLogOutputs) Write(data []byte) (int, error) {
var record map[string]any
if err := json.Unmarshal(data, &record); err != nil {
return 0, fmt.Errorf("invalid record: %w", err)
}
output.Records = append(output.Records, record)
return len(data), nil
}
22 changes: 12 additions & 10 deletions cmd/server/config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package main

import (
"os"
"path/filepath"
"time"

"github.com/davidmdm/conf"
Expand All @@ -9,10 +11,8 @@ import (
)

type Config struct {
Port string
GracefulShutdown time.Duration

Debug bool
Port string
GracePeriod time.Duration

PluginToken string

Expand All @@ -24,20 +24,22 @@ type Config struct {
}
}

func GetConfig() (cfg Config) {
defer conf.Environ.MustParse()
func GetConfig() Config {
var cfg Config

conf.Var(conf.Environ, &cfg.Port, "PORT", conf.Default(":3000"))
conf.Var(conf.Environ, &cfg.Debug, "DEBUG")
conf.Var(conf.Environ, &cfg.GracePeriod, "GRACE_PERIOD", conf.Default(10*time.Second))
conf.Var(conf.Environ, &cfg.PluginToken, "PLUGIN_TOKEN")
conf.Var(conf.Environ, &cfg.Catalog.URL, "CATALOG_REPO_URL")
conf.Var(conf.Environ, &cfg.Catalog.Path, "CATALOG_DIR")
conf.Var(conf.Environ, &cfg.Catalog.URL, "CATALOG_URL")
conf.Var(conf.Environ, &cfg.Catalog.Path, "CATALOG_DIR", conf.Default(filepath.Join(os.TempDir(), "catalog")))
conf.Var(conf.Environ, &cfg.Catalog.TargetRevision, "CATALOG_REVISION")
conf.Var(conf.Environ, &cfg.Github.User.Token, "GH_TOKEN")
conf.Var(conf.Environ, &cfg.Github.User.Name, "GH_USER")
conf.Var(conf.Environ, &cfg.Github.App.ID, "GH_APP_ID")
conf.Var(conf.Environ, &cfg.Github.App.InstallationID, "GH_APP_INSTALLATION_ID")
conf.Var(conf.Environ, &cfg.Github.App.PrivateKeyPath, "GH_APP_PRIVATE_KEY_PATH")

return
conf.Environ.MustParse()

return cfg
}
97 changes: 57 additions & 40 deletions cmd/server/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ import (
"github.com/nestoca/joy-generator/internal/github"
)

func init() {
gin.SetMode(gin.ReleaseMode)
}

type HandlerParams struct {
pluginToken string
logger zerolog.Logger
Expand All @@ -24,23 +28,69 @@ type HandlerParams struct {
func Handler(params HandlerParams) http.Handler {
engine := gin.New()

engine.Use(func(c *gin.Context) {
engine.Use(
RecoveryMiddleware(params.logger),
ObservabilityMiddleware(params.logger),
)

engine.GET("/api/v1/health", func(c *gin.Context) {
c.JSON(200, gin.H{"status": "ok"})
})

engine.GET("/api/v1/readiness", func(c *gin.Context) {
if err := params.repo.Pull(); err != nil {
c.JSON(500, gin.H{
"status": "error",
"detail": err.Error(),
})
return
}

c.JSON(200, gin.H{"status": "ok"})
})

generatorAPI := generator.API{
Logger: params.logger,
Generator: params.generator,
}

engine.POST(
"/api/v1/getparams.execute",
func(c *gin.Context) {
if c.GetHeader("Authorization") != "Bearer "+params.pluginToken {
c.AbortWithStatusJSON(401, gin.H{"error": "invalid token"})
}
},
generatorAPI.HandleGetParams,
)

return engine.Handler()
}

func RecoveryMiddleware(logger zerolog.Logger) gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
err := recover()
if err == nil {
return
}
params.logger.Err(fmt.Errorf("%v", err)).Msg("recovered from panic")
logger.Err(fmt.Errorf("%v", err)).Msg("recovered from panic")

if c.Writer.Written() {
return
}

c.JSON(500, gin.H{"error": err})
}()
})
// Important: c.Next() is needed so that defer statement doesn't execute immediately
// but only after middleware chain is complete or has panicked.
// Great catch by Mr Silphid
c.Next()
}
}

engine.Use(func(c *gin.Context) {
func ObservabilityMiddleware(logger zerolog.Logger) gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()

recorder := ErrorRecorder{
Expand All @@ -54,51 +104,18 @@ func Handler(params HandlerParams) http.Handler {

event := func() *zerolog.Event {
if err := recorder.buffer.String(); err != "" {
return params.logger.Err(errors.New(err))
return logger.Err(errors.New(err))
}
return params.logger.Info()
return logger.Info()
}()

event.
Str("method", c.Request.Method).
Str("path", c.Request.URL.Path).
Int("code", c.Writer.Status()).
Dur("elapsed", time.Since(start)).
Str("elapsed", time.Since(start).String()).
Msg("served request")
})

engine.GET("/api/v1/health", func(c *gin.Context) {
c.JSON(200, gin.H{"status": "ok"})
})

engine.GET("/api/v1/readiness", func(c *gin.Context) {
if err := params.repo.Pull(); err != nil {
c.JSON(500, gin.H{
"status": "error",
"detail": err.Error(),
})
return
}

c.JSON(200, gin.H{"status": "ok"})
})

generatorAPI := generator.API{
Logger: params.logger,
Generator: params.generator,
}

engine.GET(
"/api/v1/getparams.execute",
func(c *gin.Context) {
if c.GetHeader("Authorization") != "Bearer "+params.pluginToken {
c.AbortWithStatusJSON(401, gin.H{"error": "invalid token"})
}
},
generatorAPI.HandleGetParams,
)

return engine.Handler()
}

type ErrorRecorder struct {
Expand Down
5 changes: 4 additions & 1 deletion cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ func run() error {
return fmt.Errorf("failed to create repo: %w", err)
}

logger.Info().Str("catalog_path", repo.Metadata.Path).Msg("initialized repo")

repo = repo.WithLogger(logger)

server := &http.Server{
Expand All @@ -61,6 +63,7 @@ func run() error {
errChan := make(chan error, 1)

go func() {
logger.Info().Str("address", server.Addr).Msg("starting server")
if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
errChan <- err
}
Expand All @@ -75,7 +78,7 @@ func run() error {
case <-ctx.Done():
}

shutdownContext, cancel := context.WithTimeout(context.Background(), cfg.GracefulShutdown)
shutdownContext, cancel := context.WithTimeout(context.Background(), cfg.GracePeriod)
defer cancel()

if err := server.Shutdown(shutdownContext); err != nil {
Expand Down
16 changes: 8 additions & 8 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ require (
github.com/davidmdm/x/xcontext v0.0.2
github.com/gin-gonic/gin v1.9.1
github.com/go-git/go-git/v5 v5.11.0
github.com/nestoca/joy v0.39.0
github.com/nestoca/joy v0.39.1
github.com/rs/zerolog v1.32.0
github.com/stretchr/testify v1.9.0
gopkg.in/yaml.v3 v3.0.1
Expand All @@ -20,7 +20,7 @@ require (
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/ProtonMail/go-crypto v1.0.0 // indirect
github.com/TwiN/go-color v1.4.1 // indirect
github.com/bytedance/sonic v1.11.0 // indirect
github.com/bytedance/sonic v1.11.3 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
github.com/chenzhuoyu/iasm v0.9.1 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
Expand All @@ -35,7 +35,7 @@ require (
github.com/go-git/go-billy/v5 v5.5.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.18.0 // indirect
github.com/go-playground/validator/v10 v10.19.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
Expand All @@ -46,7 +46,7 @@ require (
github.com/json-iterator/go v1.1.12 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
Expand All @@ -55,11 +55,11 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/nestoca/survey/v2 v2.0.0 // indirect
github.com/pelletier/go-toml/v2 v2.1.1 // indirect
github.com/pelletier/go-toml/v2 v2.2.0 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/sergi/go-diff v1.3.1 // indirect
github.com/skeema/knownhosts v1.2.1 // indirect
github.com/skeema/knownhosts v1.2.2 // indirect
github.com/spf13/cobra v1.8.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
Expand All @@ -72,8 +72,8 @@ require (
golang.org/x/sys v0.18.0 // indirect
golang.org/x/term v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.18.0 // indirect
google.golang.org/protobuf v1.32.0 // indirect
golang.org/x/tools v0.19.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/godo.v2 v2.0.9 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
)
Loading

0 comments on commit 9eb1906

Please sign in to comment.