Merged in feature/docresult (pull request #105)

Document Result Endpoint

* testing

* cleantesting

* progress

* query

* lint

* readme

* dockerclient

* api

* passtest

* tests

* test
This commit is contained in:
Michael McGuinness
2025-03-17 18:14:15 +00:00
parent f7c41c4ef3
commit 555b6d420b
105 changed files with 3276 additions and 2484 deletions
+159
View File
@@ -0,0 +1,159 @@
package api
import (
"context"
"errors"
"log/slog"
"net"
"strconv"
"time"
"queryorchestration/internal/server"
"github.com/getkin/kin-openapi/openapi3"
"github.com/labstack/echo-contrib/echoprometheus"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
_ "github.com/lib/pq"
echoSwagger "github.com/swaggo/echo-swagger"
)
type Config interface {
server.Config
SetRouter(*echo.Echo)
GetRouter() *echo.Echo
RegisterHandlers() (*openapi3.T, error)
}
type BaseConfig struct {
server.BaseConfig
RegisterHandlersFunc func() (*openapi3.T, error)
OpenAPI *openapi3.T
Router *echo.Echo
}
func (c *BaseConfig) SetRouter(e *echo.Echo) {
c.Router = e
}
func (c *BaseConfig) GetRouter() *echo.Echo {
return c.Router
}
func (c *BaseConfig) RegisterHandlers() (*openapi3.T, error) {
if c.RegisterHandlersFunc == nil {
return nil, errors.New("registerHandlersFunc required")
}
o, err := c.RegisterHandlersFunc()
if err != nil {
return nil, err
}
c.OpenAPI = o
return c.OpenAPI, nil
}
type Server struct {
port int
host string
address string
router *echo.Echo
cleanup func() error
}
func getSlogCustomEchoMiddleware(logger *slog.Logger) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
start := time.Now()
err := next(c)
if err != nil {
c.Error(err)
}
req := c.Request()
res := c.Response()
logger.Info("HTTP request",
"method", req.Method,
"uri", req.RequestURI,
"status", res.Status,
"latency", time.Since(start).String(),
"error", err,
"remote_ip", c.RealIP(),
)
return err
}
}
}
func New(ctx context.Context, cfg Config) (*Server, error) {
cleanup, err := server.New(ctx, cfg)
if err != nil {
return nil, err
}
e := echo.New()
// Add Prometheus middleware
e.Use(echoprometheus.NewMiddleware("echo")) // Add the middleware
e.GET("/metrics", echoprometheus.NewHandler())
// Other middlewares
e.Use(getSlogCustomEchoMiddleware(cfg.GetLogger()))
e.Use(middleware.Recover())
e.Use(middleware.CORS())
cfg.SetRouter(e)
opnapi, err := cfg.RegisterHandlers()
if err != nil {
return nil, err
}
jsonFunc, err := getSwaggerJSONFunc(opnapi)
if err != nil {
return nil, err
}
e.GET("/swagger/doc.json", jsonFunc)
yamlFunc, err := getSwaggerYAMLFunc(opnapi)
if err != nil {
return nil, err
}
e.GET("/swagger/doc.yaml", yamlFunc)
e.GET("/swagger/*", echoSwagger.EchoWrapHandler(
echoSwagger.DocExpansion("full"),
echoSwagger.DeepLinking(true),
echoSwagger.DomID("swagger-ui"),
echoSwagger.PersistAuthorization(true),
))
host := "0.0.0.0"
port := 8080
address := net.JoinHostPort(host, strconv.Itoa(port))
return &Server{
port: port,
host: host,
address: address,
router: e,
cleanup: cleanup,
}, nil
}
func (s *Server) Listen() {
slog.Info("Listening for requests", "port", s.port)
defer func() {
if err := s.cleanup(); err != nil {
// Log cleanup error but don't panic since we're shutting down
s.router.Logger.Errorf("Error during cleanup: %v", err)
}
}()
err := s.router.Start(s.address)
if err != nil {
s.router.Logger.Panic(err)
}
}
+114
View File
@@ -0,0 +1,114 @@
package api
import (
"context"
"log/slog"
"net/http"
"net/http/httptest"
"os"
"path"
"strings"
"testing"
"queryorchestration/internal/serviceconfig/logger"
"queryorchestration/internal/test"
"github.com/getkin/kin-openapi/openapi3"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNew(t *testing.T) {
if testing.Short() {
t.Skip("Skipping long test in short mode")
}
ctx := context.Background()
cfg := &BaseConfig{}
test.SetCfgProvider(t, cfg)
cfg.SetBasePath(path.Join(os.Getenv("PWD"), "../../.."))
_, cleanup := test.CreateDB(t, ctx, &test.CreateDatabaseConfig{
Cfg: cfg,
})
defer cleanup()
registerHandlers := func() (*openapi3.T, error) {
return nil, nil
}
cfg.RegisterHandlersFunc = registerHandlers
serverInstance, err := New(ctx, cfg)
assert.NoError(t, err)
assert.Equal(t, 8080, serverInstance.port)
assert.Equal(t, "0.0.0.0", serverInstance.host)
assert.Equal(t, "0.0.0.0:8080", serverInstance.address)
}
func TestListen(t *testing.T) {
server := Server{}
assert.Panics(t, func() { server.Listen() })
}
func TestRegisterHandlers(t *testing.T) {
c := BaseConfig{}
_, err := c.RegisterHandlers()
assert.Error(t, err)
c.RegisterHandlersFunc = func() (*openapi3.T, error) {
return &openapi3.T{}, nil
}
op, err := c.RegisterHandlers()
assert.NoError(t, err)
assert.Equal(t, &openapi3.T{}, op)
assert.Equal(t, op, c.OpenAPI)
}
func TestSetRouter(t *testing.T) {
c := BaseConfig{}
assert.Nil(t, c.Router)
c.SetRouter(&echo.Echo{})
assert.Equal(t, &echo.Echo{}, c.Router)
}
func TestGetRouter(t *testing.T) {
c := BaseConfig{}
r := c.GetRouter()
assert.Nil(t, r)
assert.Equal(t, r, c.Router)
c.Router = &echo.Echo{}
r = c.GetRouter()
assert.Equal(t, &echo.Echo{}, r)
assert.Equal(t, r, c.Router)
}
func TestGetSlogCustomEchoMiddleware(t *testing.T) {
l := &logger.TestLogger{
T: t,
}
sl := slog.New(l)
handlerFunc := getSlogCustomEchoMiddleware(sl)
require.NotNil(t, handlerFunc)
mid := func(c echo.Context) error {
sl.Info("in middleware", "location", "middleware")
return nil
}
myFunc := handlerFunc(mid)
require.NotNil(t, myFunc)
uri := "/example"
req := httptest.NewRequest(http.MethodPost, uri, strings.NewReader("hello"))
rec := httptest.NewRecorder()
ctx := echo.New().NewContext(req, rec)
err := myFunc(ctx)
require.Nil(t, err)
assert.Len(t, l.Logs, 2)
assert.Equal(t, "middleware", l.Logs[0]["location"])
assert.Equal(t, uri, l.Logs[1]["uri"])
}
+35
View File
@@ -0,0 +1,35 @@
package api
import (
"net/http"
"github.com/getkin/kin-openapi/openapi3"
"github.com/labstack/echo/v4"
"gopkg.in/yaml.v3"
)
func getSwaggerJSONFunc(opnapi *openapi3.T) (func(echo.Context) error, error) {
spec, err := opnapi.MarshalJSON()
if err != nil {
return nil, err
}
return func(c echo.Context) error {
return c.Blob(http.StatusOK, "application/json", spec)
}, nil
}
func getSwaggerYAMLFunc(opnapi *openapi3.T) (func(echo.Context) error, error) {
spec, err := opnapi.MarshalYAML()
if err != nil {
return nil, err
}
yaml, err := yaml.Marshal(spec)
if err != nil {
return nil, err
}
return func(c echo.Context) error {
return c.Blob(http.StatusOK, "application/yaml", yaml)
}, nil
}
+48
View File
@@ -0,0 +1,48 @@
package api
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
queryapi "queryorchestration/api/queryAPI"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGetSwaggerJSONFunc(t *testing.T) {
openapi, err := queryapi.GetSwagger()
require.NoError(t, err)
f, err := getSwaggerJSONFunc(openapi)
require.NoError(t, err)
require.NotNil(t, f)
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader("hello"))
rec := httptest.NewRecorder()
ctx := echo.New().NewContext(req, rec)
err = f(ctx)
require.NoError(t, err)
assert.Equal(t, http.StatusOK, rec.Result().StatusCode)
}
func TestGetSwaggerYAMLFunc(t *testing.T) {
openapi, err := queryapi.GetSwagger()
require.NoError(t, err)
f, err := getSwaggerYAMLFunc(openapi)
require.NoError(t, err)
require.NotNil(t, f)
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader("hello"))
rec := httptest.NewRecorder()
ctx := echo.New().NewContext(req, rec)
err = f(ctx)
require.NoError(t, err)
assert.Equal(t, http.StatusOK, rec.Result().StatusCode)
}