Merged in bugfix/cleanup (pull request #10)

Clean Up Testing and Linting

* somescriptcleanup

* mostgolangci

* deplatest

* imageversions

* usingscratch

* movedqueuefrtesting

* finishedunittesting

* linting

* taskfilecontext
This commit is contained in:
Michael McGuinness
2025-01-10 11:12:03 +00:00
parent 57764272c3
commit 0ebb8a21a1
62 changed files with 940 additions and 357 deletions
+1
View File
@@ -4,6 +4,7 @@ export DB_PASS="${DB_PASS:-pass}"
export DB_HOST="${DB_HOST:-localhost}"
export DB_PORT="${DB_PORT:-5432}"
export DB_NAME="${DB_NAME:-query_orchestration}"
export DB_URI="${DB_URI:-"postgres://${DB_USER}:${DB_PASS}@${DB_HOST}:${DB_PORT}/${DB_NAME}?sslmode=disable"}"
dotenv_if_exists .env
+3 -2
View File
@@ -1,7 +1,8 @@
---
with-expecter: true
packages:
queryorchestration/internal:
queryorchestration/internal/database/repository:
config:
recursive: true
all: true
dir: "mocks/{{.PackageName}}"
dir: "mocks/{{.PackageName}}"
+1 -1
View File
@@ -1,4 +1,4 @@
---
extends: default
ignore: |
vendor/
vendor/
+1
View File
@@ -1,3 +1,4 @@
---
# https://taskfile.dev
version: '3'
-48
View File
@@ -1,48 +0,0 @@
package queue
import (
"context"
"log"
"queryorchestration/internal/queue"
"github.com/aws/aws-sdk-go-v2/service/sqs"
)
type Controllers struct {
Document DocumentController
}
type Queue struct {
Config *queue.QueueConfig
Controllers *Controllers
}
func PollMessages(ctx context.Context, queue *Queue) {
for {
result, err := queue.Config.Client.ReceiveMessage(ctx, &sqs.ReceiveMessageInput{
QueueUrl: &queue.Config.URL,
MaxNumberOfMessages: 1,
WaitTimeSeconds: 2,
VisibilityTimeout: 2,
MessageAttributeNames: []string{
"type",
},
})
if err != nil {
log.Printf("Message Fetch Fail: %v", err)
continue
}
for _, message := range result.Messages {
go func() {
toProcess, err := processMessage(ctx, queue, message)
if !toProcess {
return
}
if err != nil {
log.Printf("Message Process Fail: %v", err)
}
}()
}
}
}
-18
View File
@@ -1,18 +0,0 @@
package queue
import (
"context"
"github.com/aws/aws-sdk-go-v2/service/sqs/types"
)
func processMessage(ctx context.Context, queue *Queue, message types.Message) (bool, error) {
// Process the message here and return false if not message to process
switch *message.MessageAttributes["type"].StringValue {
case "DOCTEXT":
err := queue.Controllers.Document.Sync(ctx, queue.Config, &message)
return true, err
}
return false, nil
}
@@ -1,4 +1,4 @@
package queue
package controllers
import (
"context"
@@ -8,17 +8,18 @@ import (
"github.com/go-playground/validator/v10"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/sqs/types"
"github.com/google/uuid"
)
type DocumentController struct {
type QueryRunner struct {
validator *validator.Validate
document document.Service
document *document.Service
}
func NewDocumentController(svc document.Service, validator *validator.Validate) *DocumentController {
return &DocumentController{
func NewQueryRunner(svc *document.Service, validator *validator.Validate) QueryRunner {
return QueryRunner{
validator: validator,
document: svc,
}
@@ -28,7 +29,7 @@ type DocumentQueryEvent struct {
ID uuid.UUID `json:"id"`
}
func (s *DocumentController) Sync(ctx context.Context, config *queue.QueueConfig, msg *types.Message) error {
func (s QueryRunner) Process(ctx context.Context, config *queue.Config, msg *types.Message) error {
var body document.Document
err := json.Unmarshal([]byte(*msg.Body), &body)
if err != nil {
@@ -49,12 +50,12 @@ func (s *DocumentController) Sync(ctx context.Context, config *queue.QueueConfig
ID: body.ID,
}
err = queue.Send(ctx, config, "DOCQUERY", queryEvent)
if err != nil {
return err
}
err = queue.Delete(ctx, config, msg)
err = queue.Send(ctx, config, queryEvent, map[string]types.MessageAttributeValue{
"type": {
DataType: aws.String("String"),
StringValue: aws.String("DOCQUERY"),
},
})
if err != nil {
return err
}
+3 -66
View File
@@ -1,85 +1,22 @@
# syntax=docker/dockerfile:1
# Comments are provided throughout this file to help you get started.
# If you need more help, visit the Dockerfile reference guide at
# https://docs.docker.com/go/dockerfile-reference/
# Want to help us make this template better? Share your feedback here: https://forms.gle/ybq9Krt8jtBL3iCk7
################################################################################
# Create a stage for building the application.
ARG GO_VERSION=1.23
#FROM --platform=$BUILDPLATFORM golang:${GO_VERSION} AS build
FROM golang:${GO_VERSION} AS build
WORKDIR /src
# Download dependencies as a separate step to take advantage of Docker's caching.
# Leverage a cache mount to /go/pkg/mod/ to speed up subsequent builds.
# Leverage bind mounts to go.sum and go.mod to avoid having to copy them into
# the container.
RUN --mount=type=cache,target=/go/pkg/mod/ \
--mount=type=bind,source=go.sum,target=go.sum \
--mount=type=bind,source=go.mod,target=go.mod \
go mod download -x
# This is the architecture you're building for, which is passed in by the builder.
# Placing it here allows the previous steps to be cached across architectures.
ARG TARGETARCH
ARG TARGETCMD
RUN if [ -z "$TARGETCMD" ]; then \
echo "Error: TARGETCMD is not set."; \
exit 1; \
fi
# Build the application.
# Leverage a cache mount to /go/pkg/mod/ to speed up subsequent builds.
# Leverage a bind mount to the current directory to avoid having to copy the
# source code into the container.
RUN --mount=type=cache,target=/go/pkg/mod/ \
--mount=type=bind,target=. \
CGO_ENABLED=0 GOARCH=$TARGETARCH go build -o /bin/server ./cmd/$TARGETCMD/main.go
CGO_ENABLED=0 GOARCH=$TARGETARCH go build -o /bin ./cmd/...
################################################################################
# Create a new stage for running the application that contains the minimal
# runtime dependencies for the application. This often uses a different base
# image from the build stage where the necessary files are copied from the build
# stage.
#
# The example below uses the alpine image as the foundation for running the app.
# By specifying the "latest" tag, it will also use whatever happens to be the
# most recent version of that image when you build your Dockerfile. If
# reproducability is important, consider using a versioned tag
# (e.g., alpine:3.17.2) or SHA (e.g., alpine@sha256:c41ab5c992deb4fe7e5da09f67a8804a46bd0592bfdf0b1847dde0e0889d2bff).
FROM alpine:latest AS final
# Install any runtime dependencies that are needed to run your application.
# Leverage a cache mount to /var/cache/apk/ to speed up subsequent builds.
RUN --mount=type=cache,target=/var/cache/apk \
apk --update add \
ca-certificates \
tzdata \
&& \
update-ca-certificates
# Create a non-privileged user that the app will run under.
# See https://docs.docker.com/go/dockerfile-user-best-practices/
ARG UID=10001
RUN adduser \
--disabled-password \
--gecos "" \
--home "/nonexistent" \
--shell "/sbin/nologin" \
--no-create-home \
--uid "${UID}" \
appuser
USER appuser
FROM scratch AS final
COPY database/migrations/ database/migrations/
# Copy the executable from the "build" stage.
COPY --from=build /bin/server /bin/
# What the container should run when it is started.
ENTRYPOINT [ "/bin/server" ]
COPY --from=build /bin/ /bin/
+10 -42
View File
@@ -2,56 +2,24 @@ package main
import (
"context"
"log"
"queryorchestration/api/queue"
"queryorchestration/internal/database"
"queryorchestration/internal/database/repository"
controllers "queryorchestration/api/queue"
"queryorchestration/internal/document"
"queryorchestration/internal/env"
"queryorchestration/internal/otel"
queueSVC "queryorchestration/internal/queue"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/sqs"
"github.com/go-playground/validator/v10"
"queryorchestration/internal/queue"
"queryorchestration/internal/server"
)
func main() {
ctx := context.Background()
closeTracer := otel.New(ctx)
defer closeTracer()
queryrunner := func(cfg *server.Config) queue.Controller {
svc := document.New(cfg.Database)
database.RunMigrations(&database.MigrationConfig{})
cfg, err := config.LoadDefaultConfig(ctx)
if err != nil {
log.Panicf("Unable to load SDK config: %v", err)
return controllers.NewQueryRunner(svc, cfg.Validator)
}
queueURL := env.GetPanic("QUEUE_URL")
server := queue.NewServer(ctx, &queue.ListenerConfig{
Controller: queryrunner,
})
sqsClient := sqs.NewFromConfig(cfg)
dbPool := database.GetDBPool(ctx)
dbQueries := repository.New(dbPool)
db := &database.Connection{
Pool: dbPool,
Queries: dbQueries,
}
valid := validator.New()
controllers := queue.Controllers{
Document: *queue.NewDocumentController(*document.New(db), valid),
}
config := queue.Queue{
Controllers: &controllers,
Config: &queueSVC.QueueConfig{
URL: queueURL,
Client: sqsClient,
},
}
queue.PollMessages(ctx, &config)
server.Listen(ctx)
}
+13 -35
View File
@@ -2,19 +2,14 @@ package main
import (
"context"
"log"
"net"
"queryorchestration/api/controllers"
controllers "queryorchestration/api/api"
serviceinterfaces "queryorchestration/api/serviceInterfaces"
"queryorchestration/internal/api"
"queryorchestration/internal/collector"
"queryorchestration/internal/database"
"queryorchestration/internal/database/repository"
"queryorchestration/internal/export"
"queryorchestration/internal/otel"
"queryorchestration/internal/query"
"strconv"
"queryorchestration/internal/server"
"github.com/go-playground/validator/v10"
_ "github.com/lib/pq"
"google.golang.org/grpc"
)
@@ -22,35 +17,18 @@ import (
func main() {
ctx := context.Background()
closeTracer := otel.New(ctx)
defer closeTracer()
controllers := func(cfg *server.Config) *grpc.Server {
grpcServer := grpc.NewServer()
serviceinterfaces.RegisterQueryServiceServer(grpcServer, controllers.NewQueryController(*query.New(cfg.Database), cfg.Validator))
serviceinterfaces.RegisterExportServiceServer(grpcServer, controllers.NewExportController(*export.New(cfg.Database), cfg.Validator))
serviceinterfaces.RegisterJobCollectorServiceServer(grpcServer, controllers.NewJobCollectorController(*collector.New(cfg.Database), cfg.Validator))
database.RunMigrations(&database.MigrationConfig{})
host := "0.0.0.0"
port := 8080
address := net.JoinHostPort(host, strconv.Itoa(port))
lis, err := net.Listen("tcp", address)
if err != nil {
log.Panicf("failed to listen: %v", err)
return grpcServer
}
dbPool := database.GetDBPool(ctx)
dbQueries := repository.New(dbPool)
db := &database.Connection{
Pool: dbPool,
Queries: dbQueries,
}
valid := validator.New()
server := api.New(ctx, &api.Config{
GetControllers: controllers,
})
grpcServer := grpc.NewServer()
serviceinterfaces.RegisterQueryServiceServer(grpcServer, controllers.NewQueryController(*query.New(db), valid))
serviceinterfaces.RegisterExportServiceServer(grpcServer, controllers.NewExportController(*export.New(db), valid))
serviceinterfaces.RegisterJobCollectorServiceServer(grpcServer, controllers.NewJobCollectorController(*collector.New(db), valid))
log.Printf("Listening on port %d", port)
if err := grpcServer.Serve(lis); err != nil {
log.Panicf("Failed to serve: %v", err)
}
server.Listen()
}
+3 -2
View File
@@ -1,6 +1,7 @@
---
services:
db:
image: postgres:latest
image: postgres:17.2-alpine3.21
ports:
- 5432:5432
environment:
@@ -12,7 +13,7 @@ services:
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: [ "CMD-SHELL", "pg_isready -U ${DB_USER}" ]
test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
interval: 10s
timeout: 5s
retries: 5
+57
View File
@@ -0,0 +1,57 @@
package api
import (
"context"
"log"
"net"
"queryorchestration/internal/server"
"strconv"
_ "github.com/lib/pq"
"google.golang.org/grpc"
)
type Config struct {
GetControllers func(*server.Config) *grpc.Server
BasePath string
}
type Server struct {
controllers *grpc.Server
port int
host string
address string
}
func New(ctx context.Context, cfg *Config) *Server {
serverCfg := server.New(ctx, &server.NewConfig{
BasePath: cfg.BasePath,
})
host := "0.0.0.0"
port := 8080
address := net.JoinHostPort(host, strconv.Itoa(port))
controllers := cfg.GetControllers(serverCfg)
return &Server{
controllers: controllers,
port: port,
host: host,
address: address,
}
}
func (s *Server) Listen() {
lis, err := net.Listen("tcp", s.address)
if err != nil {
log.Panicf("failed to listen: %v", err)
}
log.Printf("Listening on port %d", s.port)
err = s.controllers.Serve(lis)
if err != nil {
log.Panicf("Failed to serve: %v", err)
}
}
+106
View File
@@ -0,0 +1,106 @@
package api
import (
"context"
"fmt"
"queryorchestration/internal/database"
"queryorchestration/internal/server"
"testing"
"github.com/docker/go-connections/nat"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/stretchr/testify/assert"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
"google.golang.org/grpc"
)
func TestNew(t *testing.T) {
ctx := context.Background()
_, cleanup := createDB(t, ctx)
defer cleanup()
getControllers := func(c *server.Config) *grpc.Server {
return nil
}
cfg := &Config{
GetControllers: getControllers,
BasePath: "../..",
}
server := New(ctx, cfg)
assert.Equal(t, 8080, server.port)
assert.Equal(t, "0.0.0.0", server.host)
assert.Equal(t, "0.0.0.0:8080", server.address)
}
func TestListen(t *testing.T) {
t.Skip("Must test appropriately")
server := Server{}
assert.Panics(t, func() { server.Listen() })
}
type db struct {
pool *pgxpool.Pool
container *testcontainers.Container
}
func createDB(t *testing.T, ctx context.Context) (*db, func()) {
port, err := nat.NewPort("tcp", "5432")
if err != nil {
t.Fatalf("Failed to create port: %v", err)
}
name := "queryorchestration"
pass := "pass"
user := "postgres"
req := testcontainers.ContainerRequest{
Image: "postgres:17.2-alpine3.21",
Env: map[string]string{
"POSTGRES_DB": name,
"POSTGRES_USER": user,
"POSTGRES_PASSWORD": pass,
},
ExposedPorts: []string{port.Port()},
WaitingFor: wait.ForListeningPort(port),
}
container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: req,
Started: true,
})
if err != nil {
t.Fatalf("Failed to start container: %v", err)
}
host, err := container.Host(ctx)
if err != nil {
t.Fatalf("Failed to extract host: %v", err)
}
mappedPort, err := container.MappedPort(ctx, port)
if err != nil {
t.Fatalf("Failed to extract port: %v", err)
}
t.Setenv("DB_USER", user)
t.Setenv("DB_PASS", pass)
t.Setenv("DB_HOST", host)
t.Setenv("DB_PORT", fmt.Sprint(mappedPort.Int()))
t.Setenv("DB_NAME", name)
t.Setenv("DB_NOSSL", "1")
pool := database.GetDBPool(ctx)
return &db{
pool: pool,
container: &container,
}, func() {
err := container.Terminate(ctx)
if err != nil {
t.Error(err)
}
}
}
+2 -2
View File
@@ -2,7 +2,7 @@ package contextfull
import (
"context"
"fmt"
"errors"
queryprocessor "queryorchestration/internal/queryProcessor"
"queryorchestration/internal/result"
)
@@ -16,7 +16,7 @@ func NewExtractor() Extractor {
func (e Extractor) Process(ctx context.Context, query *queryprocessor.Query, values []result.Value) (string, error) {
if len(values) > 0 {
return "", fmt.Errorf("no requirements expected")
return "", errors.New("no requirements expected")
}
// TODO
+20 -7
View File
@@ -14,8 +14,8 @@ import (
func TestDBConn(t *testing.T) {
ctx := context.Background()
_, container := createDB(t, ctx)
defer container.Terminate(ctx)
_, cleanup := createDB(t, ctx)
defer cleanup()
conn := database.GetDBConn(ctx)
assert.NotNil(t, conn)
@@ -34,8 +34,8 @@ func TestDBConnNoDB(t *testing.T) {
func TestDBPool(t *testing.T) {
ctx := context.Background()
_, container := createDB(t, ctx)
defer container.Terminate(ctx)
_, cleanup := createDB(t, ctx)
defer cleanup()
pool := database.GetDBPool(ctx)
assert.NotNil(t, pool)
@@ -49,7 +49,12 @@ type dbConfig struct {
Password string
}
func createDB(t *testing.T, ctx context.Context) (*dbConfig, testcontainers.Container) {
type db struct {
config *dbConfig
container *testcontainers.Container
}
func createDB(t *testing.T, ctx context.Context) (*db, func()) {
alias := "postgres"
port, err := nat.NewPort("tcp", "5432")
if err != nil {
@@ -65,7 +70,7 @@ func createDB(t *testing.T, ctx context.Context) (*dbConfig, testcontainers.Cont
}
req := testcontainers.ContainerRequest{
Image: "postgres:latest",
Image: "postgres:17.2-alpine3.21",
Env: map[string]string{
"POSTGRES_DB": config.Name,
"POSTGRES_USER": config.User,
@@ -102,5 +107,13 @@ func createDB(t *testing.T, ctx context.Context) (*dbConfig, testcontainers.Cont
t.Setenv("DB_PORT", fmt.Sprint(config.Port))
t.Setenv("DB_NAME", config.Name)
return &config, container
return &db{
config: &config,
container: &container,
}, func() {
err := container.Terminate(ctx)
if err != nil {
t.Error(err)
}
}
}
+2 -2
View File
@@ -12,8 +12,8 @@ import (
func TestRunMigrations(t *testing.T) {
ctx := context.Background()
_, container := createDB(t, ctx)
defer container.Terminate(ctx)
_, cleanup := createDB(t, ctx)
defer cleanup()
t.Setenv("DB_NOSSL", "1")
+7 -1
View File
@@ -1,6 +1,8 @@
package database
import (
"log"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgtype"
)
@@ -16,7 +18,11 @@ func MustToDBUUIDArray(ids []uuid.UUID) []pgtype.UUID {
func MustToDBUUID(id uuid.UUID) pgtype.UUID {
var dbID pgtype.UUID
dbID.Scan(id.String())
err := dbID.Scan(id.String())
if err != nil {
log.Panic(err)
}
return dbID
}
+2 -2
View File
@@ -23,7 +23,7 @@ func TestMustToDBUUIDArray(t *testing.T) {
dbIDs := database.MustToDBUUIDArray(ids)
assert.Equal(t, len(ids), len(dbIDs))
assert.Len(t, dbIDs, len(ids))
for index, id := range dbIDs {
assert.Equal(t, database.MustToDBUUID(ids[index]), id)
}
@@ -42,7 +42,7 @@ func TestMustToUUIDArray(t *testing.T) {
ids := database.MustToUUIDArray(dbIDs)
assert.Equal(t, len(ids), len(dbIDs))
assert.Len(t, ids, len(dbIDs))
for index, id := range dbIDs {
assert.Equal(t, database.MustToDBUUID(ids[index]), id)
}
@@ -12,15 +12,15 @@ import (
func TestCollector(t *testing.T) {
ctx := context.Background()
pool, container := createDB(t, ctx)
defer container.Terminate(ctx)
queries := repository.New(pool)
db, cleanup := createDB(t, ctx)
defer cleanup()
queries := repository.New(db.pool)
collectorID := database.MustToDBUUID(uuid.New())
collectorQueries, err := queries.GetCollectorQueries(ctx, collectorID)
assert.Nil(t, err)
assert.Equal(t, 0, len(collectorQueries))
assert.Len(t, collectorQueries, 0)
assert.ElementsMatch(t, []repository.GetCollectorQueriesRow{}, collectorQueries)
jobID := database.MustToDBUUID(uuid.New())
+4 -4
View File
@@ -12,9 +12,9 @@ import (
func TestQueries(t *testing.T) {
ctx := context.Background()
pool, container := createDB(t, ctx)
defer container.Terminate(ctx)
queries := repository.New(pool)
db, cleanup := createDB(t, ctx)
defer cleanup()
queries := repository.New(db.pool)
contextQueryID, err := queries.CreateQuery(ctx, repository.Querytype(repository.QuerytypeContextFull))
assert.Nil(t, err)
@@ -60,7 +60,7 @@ func TestQueries(t *testing.T) {
qs, err := queries.ListQueries(ctx)
assert.Nil(t, err)
assert.Equal(t, 2, len(qs))
assert.Len(t, qs, 2)
log.Print(qs)
assert.ElementsMatch(t, []repository.Fullactivequery{
{ID: jsonQueryID,
+5 -5
View File
@@ -13,9 +13,9 @@ import (
func TestResults(t *testing.T) {
ctx := context.Background()
pool, container := createDB(t, ctx)
defer container.Terminate(ctx)
queries := repository.New(pool)
db, cleanup := createDB(t, ctx)
defer cleanup()
queries := repository.New(db.pool)
jsonQueryID, err := queries.CreateQuery(ctx, repository.Querytype(repository.QuerytypeJsonExtractor))
assert.Nil(t, err)
@@ -46,7 +46,7 @@ func TestResults(t *testing.T) {
Textversion: textVersion,
})
assert.Nil(t, err)
assert.Equal(t, 1, len(resultsByDoc))
assert.Len(t, resultsByDoc, 1)
assert.ElementsMatch(t, []repository.ListResultsByDocumentIDRow{
{
ID: jsonResultID,
@@ -57,7 +57,7 @@ func TestResults(t *testing.T) {
results, err := queries.ListResultValuesByID(ctx, []pgtype.UUID{jsonResultID})
assert.Nil(t, err)
assert.Equal(t, 1, len(results))
assert.Len(t, results, 1)
assert.ElementsMatch(t, []repository.ListResultValuesByIDRow{
{
ID: jsonResultID,
+16 -3
View File
@@ -14,7 +14,12 @@ import (
"github.com/testcontainers/testcontainers-go/wait"
)
func createDB(t *testing.T, ctx context.Context) (*pgxpool.Pool, testcontainers.Container) {
type db struct {
pool *pgxpool.Pool
container *testcontainers.Container
}
func createDB(t *testing.T, ctx context.Context) (*db, func()) {
port, err := nat.NewPort("tcp", "5432")
if err != nil {
t.Fatalf("Failed to create port: %v", err)
@@ -25,7 +30,7 @@ func createDB(t *testing.T, ctx context.Context) (*pgxpool.Pool, testcontainers.
user := "postgres"
req := testcontainers.ContainerRequest{
Image: "postgres:latest",
Image: "postgres:17.2-alpine3.21",
Env: map[string]string{
"POSTGRES_DB": name,
"POSTGRES_USER": user,
@@ -65,5 +70,13 @@ func createDB(t *testing.T, ctx context.Context) (*pgxpool.Pool, testcontainers.
pool := database.GetDBPool(ctx)
return pool, container
return &db{
pool: pool,
container: &container,
}, func() {
err := container.Terminate(ctx)
if err != nil {
t.Error(err)
}
}
}
+9 -9
View File
@@ -98,13 +98,13 @@ func TestSyncDBFail(t *testing.T) {
pgxmock.NewRows([]string{"id", "jobId", "minCleanVersion", "minTextVersion"}).
AddRow(dbCollectorId, dbJobID, minCleanVersion, minTextVersion),
)
errr := "database failure"
dbErr := "database failure"
pool.ExpectQuery("name: ListResultsByDocumentID :many").WithArgs(database.MustToDBUUID(doc.ID), minCleanVersion, minTextVersion).
WillReturnError(errors.New(errr))
WillReturnError(errors.New(dbErr))
docSvc = document.New(db)
err = docSvc.Sync(ctx, &doc)
assert.EqualError(t, err, errr)
assert.EqualError(t, err, dbErr)
pool.ExpectQuery("name: GetCollectorFromJobID :one").WithArgs(dbJobID).
WillReturnRows(
@@ -115,13 +115,13 @@ func TestSyncDBFail(t *testing.T) {
WillReturnRows(
pgxmock.NewRows([]string{"id", "queryId", "queryVersion"}),
)
errr = "database failure"
dbErr = "database failure"
pool.ExpectQuery("name: GetCollectorQueries :many").WithArgs(dbCollectorId).
WillReturnError(errors.New(errr))
WillReturnError(errors.New(dbErr))
docSvc = document.New(db)
err = docSvc.Sync(ctx, &doc)
assert.EqualError(t, err, errr)
assert.EqualError(t, err, dbErr)
pool.ExpectQuery("name: GetCollectorFromJobID :one").WithArgs(dbJobID).
WillReturnRows(
@@ -132,18 +132,18 @@ func TestSyncDBFail(t *testing.T) {
WillReturnRows(
pgxmock.NewRows([]string{"id", "queryId", "queryVersion"}),
)
errr = "database failure"
dbErr = "database failure"
pool.ExpectQuery("name: GetCollectorQueries :many").WithArgs(dbCollectorId).
WillReturnRows(
pgxmock.NewRows([]string{"collectorId", "queryId", "type", "queryVersion", "requiredIds"}).
AddRow(dbCollectorId, dbQueryID, repository.NullQuerytype{Querytype: repository.QuerytypeJsonExtractor, Valid: true}, pgtype.Int4{Int32: int32(1), Valid: true}, []pgtype.UUID{}),
)
pool.ExpectQuery("name: ListResultValuesByID :many").WithArgs([]pgtype.UUID{}).
WillReturnError(errors.New(errr))
WillReturnError(errors.New(dbErr))
docSvc = document.New(db)
err = docSvc.Sync(ctx, &doc)
assert.EqualError(t, err, errr)
assert.EqualError(t, err, dbErr)
}
func TestSync(t *testing.T) {
-2
View File
@@ -164,8 +164,6 @@ func TestJSONProcessJSON(t *testing.T) {
assert.EqualError(t, err, "invalid character '}' looking for beginning of value")
assert.Empty(t, value)
config = "{\"path\":\"key\"}"
pool.ExpectQuery("name: GetQueryConfig :one").WithArgs(database.MustToDBUUID(query.ID), query.Version).
WillReturnRows(
pgxmock.NewRows([]string{"id", "config"}),
+5 -2
View File
@@ -13,7 +13,7 @@ import (
func New(ctx context.Context) func() {
if os.Getenv("ENABLE_OTEL") != "1" {
log.Println("OpenTelemetry is disabled. Set ENABLE_OTEL to enable.")
return func() {} // No-op shutdown function
return func() {}
}
exporter, err := otlptracegrpc.New(ctx)
@@ -28,6 +28,9 @@ func New(ctx context.Context) func() {
otel.SetTracerProvider(tp)
return func() {
_ = tp.Shutdown(ctx)
err := tp.Shutdown(ctx)
if err != nil {
log.Panic(err)
}
}
}
+1 -5
View File
@@ -9,7 +9,6 @@ import (
"testing"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgtype"
"github.com/pashagolub/pgxmock/v3"
"github.com/stretchr/testify/assert"
)
@@ -40,10 +39,7 @@ func TestGet(t *testing.T) {
Config: config,
}
dbReqIDs := make([]pgtype.UUID, len(query.RequiredQueryIDs))
for index, id := range query.RequiredQueryIDs {
dbReqIDs[index] = database.MustToDBUUID(id)
}
dbReqIDs := database.MustToDBUUIDArray(query.RequiredQueryIDs)
pool.ExpectQuery("name: GetQuery :one").WithArgs(database.MustToDBUUID(query.ID)).WillReturnRows(
pgxmock.NewRows([]string{"id", "type", "activeVersion", "latestVersion", "config", "requiredIds"}).
+1 -5
View File
@@ -9,7 +9,6 @@ import (
"testing"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgtype"
"github.com/pashagolub/pgxmock/v3"
"github.com/stretchr/testify/assert"
)
@@ -40,10 +39,7 @@ func TestList(t *testing.T) {
Config: config,
}
dbReqIDs := make([]pgtype.UUID, len(q.RequiredQueryIDs))
for index, id := range q.RequiredQueryIDs {
dbReqIDs[index] = database.MustToDBUUID(id)
}
dbReqIDs := database.MustToDBUUIDArray(q.RequiredQueryIDs)
filters := query.ListFilters{}
+2 -1
View File
@@ -76,7 +76,8 @@ func TestGetCollectorQueries(t *testing.T) {
pool.ExpectQuery("name: GetCollectorQueries :many").WithArgs(dbCollectorID).WillReturnRows(rows)
svc.getCollectorQueries(ctx)
err = svc.getCollectorQueries(ctx)
assert.Nil(t, err)
assert.EqualExportedValues(t, collectorQueries, svc.collectorQueries)
}
+3 -3
View File
@@ -130,9 +130,9 @@ func TestQueueFail(t *testing.T) {
coll, err := collector.NewByJobId(ctx, db, jobID)
assert.Nil(t, err)
errr := "database failure"
dbErr := "database failure"
pool.ExpectQuery("name: GetCollectorQueries :many").WithArgs(dbCollectorID).
WillReturnError(errors.New(errr))
WillReturnError(errors.New(dbErr))
results := []*result.Result{}
@@ -141,5 +141,5 @@ func TestQueueFail(t *testing.T) {
textVersion := int32(1)
_, err = queryqueue.New(ctx, db, coll, results, docID, cleanVersion, textVersion)
assert.EqualError(t, err, errr)
assert.EqualError(t, err, dbErr)
}
+8 -1
View File
@@ -1,10 +1,17 @@
package queue
import (
"context"
"github.com/aws/aws-sdk-go-v2/service/sqs"
"github.com/aws/aws-sdk-go-v2/service/sqs/types"
)
type QueueConfig struct {
type Config struct {
URL string
Client *sqs.Client
}
type Controller interface {
Process(ctx context.Context, config *Config, msg *types.Message) error
}
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"github.com/aws/aws-sdk-go-v2/service/sqs/types"
)
func Delete(ctx context.Context, config *QueueConfig, msg *types.Message) error {
func Delete(ctx context.Context, config *Config, msg *types.Message) error {
_, err := config.Client.DeleteMessage(ctx, &sqs.DeleteMessageInput{
QueueUrl: aws.String(config.URL),
ReceiptHandle: msg.ReceiptHandle,
+32
View File
@@ -0,0 +1,32 @@
package queue_test
import (
"context"
"queryorchestration/internal/queue"
"testing"
"github.com/aws/aws-sdk-go-v2/service/sqs/types"
"github.com/stretchr/testify/assert"
)
func TestDelete(t *testing.T) {
ctx := context.Background()
queueConfig, cleanup := createQueue(t, ctx)
defer cleanup()
err := queue.Send(ctx, queueConfig.Config, "{}", map[string]types.MessageAttributeValue{})
assert.Nil(t, err)
result, err := queue.Receive(ctx, queueConfig.Config, []string{})
assert.Nil(t, err)
assert.Len(t, result.Messages, 1)
message := result.Messages[0]
err = queue.Delete(ctx, queueConfig.Config, &message)
assert.Nil(t, err)
result, err = queue.Receive(ctx, queueConfig.Config, []string{})
assert.Nil(t, err)
assert.Len(t, result.Messages, 0)
}
+54
View File
@@ -0,0 +1,54 @@
package queue
import (
"context"
"log"
"queryorchestration/internal/env"
"queryorchestration/internal/server"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/sqs"
)
type ListenerConfig struct {
Controller func(*server.Config) Controller
BasePath string
}
type Server struct {
controller Controller
queueConnection *Config
}
func NewServer(ctx context.Context, lConfig *ListenerConfig) *Server {
serverCfg := server.New(ctx, &server.NewConfig{
BasePath: lConfig.BasePath,
})
cfg, err := config.LoadDefaultConfig(ctx)
if err != nil {
log.Panicf("Unable to load SDK config: %v", err)
}
queueURL := env.GetPanic("QUEUE_URL")
sqsClient := sqs.NewFromConfig(cfg)
return &Server{
controller: lConfig.Controller(serverCfg),
queueConnection: &Config{
URL: queueURL,
Client: sqsClient,
},
}
}
func (s *Server) Listen(ctx context.Context) {
config := &PollConfig{
Controller: s.controller,
Config: s.queueConnection,
}
log.Print("Listening to queue")
PollMessages(ctx, config)
}
+108
View File
@@ -0,0 +1,108 @@
package queue_test
import (
"context"
"fmt"
"queryorchestration/internal/database"
"queryorchestration/internal/queue"
"queryorchestration/internal/server"
"testing"
"time"
"github.com/docker/go-connections/nat"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/stretchr/testify/assert"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
)
func TestNew(t *testing.T) {
ctx := context.Background()
_, cleanup := createQueue(t, ctx)
defer cleanup()
_, cleanup = createDB(t, ctx)
defer cleanup()
ctx, cancel := context.WithTimeout(ctx, time.Second)
defer cancel()
controller := func(cfg *server.Config) queue.Controller {
return MockController{}
}
queue.NewServer(ctx, &queue.ListenerConfig{
Controller: controller,
BasePath: "../..",
})
}
func TestListen(t *testing.T) {
ctx := context.Background()
queue := queue.Server{}
assert.Panics(t, func() { queue.Listen(ctx) })
}
type db struct {
pool *pgxpool.Pool
container *testcontainers.Container
}
func createDB(t *testing.T, ctx context.Context) (*db, func()) {
port, err := nat.NewPort("tcp", "5432")
if err != nil {
t.Fatalf("Failed to create port: %v", err)
}
name := "queryorchestration"
pass := "pass"
user := "postgres"
req := testcontainers.ContainerRequest{
Image: "postgres:17.2-alpine3.21",
Env: map[string]string{
"POSTGRES_DB": name,
"POSTGRES_USER": user,
"POSTGRES_PASSWORD": pass,
},
ExposedPorts: []string{port.Port()},
WaitingFor: wait.ForListeningPort(port),
}
container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: req,
Started: true,
})
if err != nil {
t.Fatalf("Failed to start container: %v", err)
}
host, err := container.Host(ctx)
if err != nil {
t.Fatalf("Failed to extract host: %v", err)
}
mappedPort, err := container.MappedPort(ctx, port)
if err != nil {
t.Fatalf("Failed to extract port: %v", err)
}
t.Setenv("DB_USER", user)
t.Setenv("DB_PASS", pass)
t.Setenv("DB_HOST", host)
t.Setenv("DB_PORT", fmt.Sprint(mappedPort.Int()))
t.Setenv("DB_NAME", name)
t.Setenv("DB_NOSSL", "1")
pool := database.GetDBPool(ctx)
return &db{
pool: pool,
container: &container,
}, func() {
err := container.Terminate(ctx)
if err != nil {
t.Error(err)
}
}
}
+51
View File
@@ -0,0 +1,51 @@
package queue
import (
"context"
"fmt"
"log"
)
type PollConfig struct {
Config *Config
Controller Controller
}
func PollMessages(ctx context.Context, queueConfig *PollConfig) {
for {
select {
case <-ctx.Done():
return
default:
err := PollMessage(ctx, queueConfig)
if err != nil {
log.Print(err)
}
}
}
}
func PollMessage(ctx context.Context, queueConfig *PollConfig) error {
result, err := Receive(ctx, queueConfig.Config, []string{
"type",
})
if err != nil {
return fmt.Errorf("message fetch fail: %v", err)
}
for _, message := range result.Messages {
go func() {
err := queueConfig.Controller.Process(ctx, queueConfig.Config, &message)
if err != nil {
log.Printf("message process fail: %v", err)
}
err = Delete(ctx, queueConfig.Config, &message)
if err != nil {
log.Printf("message delete fail: %v", err)
}
}()
}
return nil
}
+47
View File
@@ -0,0 +1,47 @@
package queue_test
import (
"context"
"queryorchestration/internal/queue"
"testing"
"time"
"github.com/aws/aws-sdk-go-v2/service/sqs/types"
"github.com/stretchr/testify/assert"
)
type MockController struct{}
func (s MockController) Process(ctx context.Context, config *queue.Config, msg *types.Message) error {
return nil
}
func TestPollMessages(t *testing.T) {
ctx := context.Background()
queueConfig, cleanup := createQueue(t, ctx)
defer cleanup()
controller := MockController{}
ctx, cancel := context.WithTimeout(ctx, time.Second)
defer cancel()
queue.PollMessages(ctx, &queue.PollConfig{
Config: queueConfig.Config,
Controller: controller,
})
}
func TestPollMessage(t *testing.T) {
ctx := context.Background()
queueConfig, cleanup := createQueue(t, ctx)
defer cleanup()
controller := MockController{}
err := queue.PollMessage(ctx, &queue.PollConfig{
Config: queueConfig.Config,
Controller: controller,
})
assert.Nil(t, err)
}
+7 -4
View File
@@ -17,7 +17,7 @@ import (
type queueConfig struct {
Container *testcontainers.Container
Config *queue.QueueConfig
Config *queue.Config
}
func createQueue(t *testing.T, ctx context.Context) (*queueConfig, func()) {
@@ -32,7 +32,7 @@ func createQueue(t *testing.T, ctx context.Context) (*queueConfig, func()) {
}
req := testcontainers.ContainerRequest{
Image: "localstack/localstack:latest",
Image: "localstack/localstack:4.0.3",
Env: map[string]string{
"AWS_ACCESS_KEY_ID": provider.Value.AccessKeyID,
"AWS_SECRET_ACCESS_KEY": provider.Value.SecretAccessKey,
@@ -82,11 +82,14 @@ func createQueue(t *testing.T, ctx context.Context) (*queueConfig, func()) {
return &queueConfig{
Container: &container,
Config: &queue.QueueConfig{
Config: &queue.Config{
Client: client,
URL: *queueM.QueueUrl,
},
}, func() {
container.Terminate(ctx)
err := container.Terminate(ctx)
if err != nil {
t.Error(err)
}
}
}
+17
View File
@@ -0,0 +1,17 @@
package queue
import (
"context"
"github.com/aws/aws-sdk-go-v2/service/sqs"
)
func Receive(ctx context.Context, config *Config, attributes []string) (*sqs.ReceiveMessageOutput, error) {
return config.Client.ReceiveMessage(ctx, &sqs.ReceiveMessageInput{
QueueUrl: &config.URL,
MaxNumberOfMessages: 1,
WaitTimeSeconds: 2,
VisibilityTimeout: 2,
MessageAttributeNames: attributes,
})
}
+32
View File
@@ -0,0 +1,32 @@
package queue_test
import (
"context"
"log"
"queryorchestration/internal/queue"
"testing"
"github.com/aws/aws-sdk-go-v2/service/sqs/types"
"github.com/stretchr/testify/assert"
)
func TestReceive(t *testing.T) {
ctx := context.Background()
queueConfig, cleanup := createQueue(t, ctx)
defer cleanup()
attributes := map[string]types.MessageAttributeValue{}
err := queue.Send(ctx, queueConfig.Config, "example_body", attributes)
assert.Nil(t, err)
result, err := queue.Receive(ctx, queueConfig.Config, []string{})
assert.Nil(t, err)
assert.Len(t, result.Messages, 1)
message := result.Messages[0]
log.Print(message.Attributes)
assert.Equal(t, "\"example_body\"", *message.Body)
}
+4 -9
View File
@@ -9,7 +9,7 @@ import (
"github.com/aws/aws-sdk-go-v2/service/sqs/types"
)
func Send(ctx context.Context, config *QueueConfig, typeName string, body interface{}) error {
func Send(ctx context.Context, config *Config, body interface{}, attributes map[string]types.MessageAttributeValue) error {
jsonBytes, err := json.Marshal(body)
if err != nil {
return err
@@ -18,14 +18,9 @@ func Send(ctx context.Context, config *QueueConfig, typeName string, body interf
strBody := string(jsonBytes)
_, err = config.Client.SendMessage(ctx, &sqs.SendMessageInput{
MessageAttributes: map[string]types.MessageAttributeValue{
"type": {
DataType: aws.String("String"),
StringValue: aws.String(typeName),
},
},
QueueUrl: aws.String(config.URL),
MessageBody: aws.String(strBody),
MessageAttributes: attributes,
QueueUrl: aws.String(config.URL),
MessageBody: aws.String(strBody),
})
if err != nil {
return err
+25
View File
@@ -0,0 +1,25 @@
package queue_test
import (
"context"
"queryorchestration/internal/queue"
"testing"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/sqs/types"
"github.com/stretchr/testify/assert"
)
func TestSend(t *testing.T) {
ctx := context.Background()
queueConfig, cleanup := createQueue(t, ctx)
defer cleanup()
err := queue.Send(ctx, queueConfig.Config, "{}", map[string]types.MessageAttributeValue{
"type": {
DataType: aws.String("String"),
StringValue: aws.String("EXAMPLE_TYPE"),
},
})
assert.Nil(t, err)
}
+4 -4
View File
@@ -2,7 +2,7 @@ package result_test
import (
"context"
"fmt"
"errors"
"queryorchestration/internal/database"
"queryorchestration/internal/database/repository"
"queryorchestration/internal/result"
@@ -40,11 +40,11 @@ func TestStore(t *testing.T) {
assert.Nil(t, err)
assert.NotNil(t, id)
errr := "database failing"
dbErr := "database failing"
pool.ExpectQuery("name: SetResult :one").WithArgs(database.MustToDBUUID(resultStore.QueryID), database.MustToDBUUID(resultStore.DocumentID), resultStore.Value, resultStore.CleanVersion, resultStore.TextVersion, resultStore.QueryVersion).
WillReturnError(fmt.Errorf(errr))
WillReturnError(errors.New(dbErr))
id, err = result.Store(ctx, queries, &resultStore)
assert.EqualError(t, err, errr)
assert.EqualError(t, err, dbErr)
assert.Equal(t, uuid.Nil, id)
}
+41
View File
@@ -0,0 +1,41 @@
package server
import (
"context"
"queryorchestration/internal/database"
"queryorchestration/internal/database/repository"
"queryorchestration/internal/otel"
"github.com/go-playground/validator/v10"
)
type Config struct {
Database *database.Connection
Validator *validator.Validate
}
type NewConfig struct {
BasePath string
}
func New(ctx context.Context, cfg *NewConfig) *Config {
closeTracer := otel.New(ctx)
defer closeTracer()
database.RunMigrations(&database.MigrationConfig{
BasePath: cfg.BasePath,
})
dbPool := database.GetDBPool(ctx)
dbQueries := repository.New(dbPool)
db := &database.Connection{
Pool: dbPool,
Queries: dbQueries,
}
valid := validator.New()
return &Config{
Database: db,
Validator: valid,
}
}
+92
View File
@@ -0,0 +1,92 @@
package server_test
import (
"context"
"fmt"
"queryorchestration/internal/database"
"queryorchestration/internal/server"
"testing"
"github.com/docker/go-connections/nat"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/stretchr/testify/assert"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
)
func TestNew(t *testing.T) {
ctx := context.Background()
_, cleanup := createDB(t, ctx)
defer cleanup()
newCfg := &server.NewConfig{
BasePath: "../..",
}
cfg := server.New(ctx, newCfg)
assert.NotNil(t, cfg)
}
type db struct {
pool *pgxpool.Pool
container *testcontainers.Container
}
func createDB(t *testing.T, ctx context.Context) (*db, func()) {
port, err := nat.NewPort("tcp", "5432")
if err != nil {
t.Fatalf("Failed to create port: %v", err)
}
name := "queryorchestration"
pass := "pass"
user := "postgres"
req := testcontainers.ContainerRequest{
Image: "postgres:17.2-alpine3.21",
Env: map[string]string{
"POSTGRES_DB": name,
"POSTGRES_USER": user,
"POSTGRES_PASSWORD": pass,
},
ExposedPorts: []string{port.Port()},
WaitingFor: wait.ForListeningPort(port),
}
container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: req,
Started: true,
})
if err != nil {
t.Fatalf("Failed to start container: %v", err)
}
host, err := container.Host(ctx)
if err != nil {
t.Fatalf("Failed to extract host: %v", err)
}
mappedPort, err := container.MappedPort(ctx, port)
if err != nil {
t.Fatalf("Failed to extract port: %v", err)
}
t.Setenv("DB_USER", user)
t.Setenv("DB_PASS", pass)
t.Setenv("DB_HOST", host)
t.Setenv("DB_PORT", fmt.Sprint(mappedPort.Int()))
t.Setenv("DB_NAME", name)
t.Setenv("DB_NOSSL", "1")
pool := database.GetDBPool(ctx)
return &db{
pool: pool,
container: &container,
}, func() {
err := container.Terminate(ctx)
if err != nil {
t.Error(err)
}
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
// Code generated by mockery v2.49.1. DO NOT EDIT.
// Code generated by mockery v2.50.0. DO NOT EDIT.
package repository
+14 -1
View File
@@ -1,19 +1,26 @@
---
# https://taskfile.dev
version: '3'
includes:
deps:
dir: "{{.CONTEXT}}"
taskfile: dependencies.yml
test:
dir: "{{.CONTEXT}}"
taskfile: tests.yml
docker:
dir: "{{.CONTEXT}}"
taskfile: docker.yml
proto:
dir: "{{.CONTEXT}}"
taskfile: proto.yml
compose:
dir: "{{.CONTEXT}}"
taskfile: compose.yml
db:
dir: "{{.CONTEXT}}"
taskfile: database.yml
vars:
@@ -39,12 +46,18 @@ tasks:
cmds:
- task code:lint:fix
- task proto:lint:fix
- task db:lint
- task yaml:lint
- task docker:lint
- task compose:lint
- task shell:lint
code:lint:
cmds:
- golangci-lint run
code:lint:fix:
cmds:
- gofmt -w .
- golangci-lint run
yaml:lint:
cmds:
- yamllint . -s
@@ -53,4 +66,4 @@ tasks:
- shellcheck --shell=sh scripts/install-deps.sh
docs:
cmds:
- godoc -http=:6060
- godoc -http=:6060
+10 -3
View File
@@ -1,9 +1,10 @@
---
# https://taskfile.dev
version: '3'
vars:
COMPOSE_FILE: "{{.CONTEXT}}/deployments/compose.yaml"
COMPOSE_FILE: "deployments/compose.yaml"
tasks:
build:
@@ -11,7 +12,13 @@ tasks:
- docker compose -f {{.COMPOSE_FILE}} build
up:
cmds:
- docker compose -f {{.COMPOSE_FILE}} up
- docker compose -f {{.COMPOSE_FILE}} up --no-recreate
up:bg:
cmds:
- docker compose -f {{.COMPOSE_FILE}} up --no-recreate -d
down:
cmds:
- docker compose -f {{.COMPOSE_FILE}} down
lint:
cmds:
- docker compose -f {{.COMPOSE_FILE}} config
- docker compose -f {{.COMPOSE_FILE}} config
+7 -4
View File
@@ -1,15 +1,18 @@
---
# https://taskfile.dev
version: '3'
vars:
MIGRATIONS: "{{.CONTEXT}}/database/migrations"
DATABASE_URI: "postgres://${DB_USER}:${DB_PASS}@${DB_HOST}:${DB_PORT}/${DB_NAME}?sslmode=disable"
MIGRATIONS: "database/migrations"
tasks:
generate:
cmds:
- sqlc generate --file ../sqlc.yml
- |
task compose:up:bg
task db:mig:run
sqlc generate --file ../sqlc.yml
lint:
cmds:
- sqlc vet --file ../sqlc.yml
@@ -21,4 +24,4 @@ tasks:
migrate create -ext sql -dir {{.MIGRATIONS}} $name
mig:run:
cmds:
- migrate -path {{.MIGRATIONS}} -database {{.DATABASE_URI}} up
- migrate -path {{.MIGRATIONS}} -database {{.DB_URI}} up
+14 -11
View File
@@ -1,3 +1,4 @@
---
# https://taskfile.dev
version: '3'
@@ -5,20 +6,22 @@ version: '3'
tasks:
install:
cmds:
- go install golang.org/x/tools/cmd/godoc@latest
- go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
- go install github.com/yoheimuta/protolint/cmd/protolint@latest
- go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
- go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
- go install github.com/zabio3/godolint@latest
- go install github.com/wasilibs/go-yamllint/cmd/yamllint@latest
- go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest
- go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
- go install github.com/vektra/mockery/v2@latest
- go install golang.org/x/tools/cmd/godoc@v0.29.0
- go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.2
- go install github.com/yoheimuta/protolint/cmd/protolint@v0.52.0
- go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.36.0
- go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.5.1
- go install github.com/zabio3/godolint@v1.0.3
- go install github.com/wasilibs/go-yamllint/cmd/yamllint@v1.35.1
- go install github.com/sqlc-dev/sqlc/cmd/sqlc@v1.27.0
- |
go install -tags 'postgres' \
github.com/golang-migrate/migrate/v4/cmd/migrate@v4.18.1
- go install github.com/vektra/mockery/v2@v2.50.0
- curl -sfL https://direnv.net/install.sh | bash
- direnv allow
- go mod download
tidy:
cmds:
- go mod tidy
- go mod vendor
- go mod vendor
+3 -3
View File
@@ -1,9 +1,10 @@
---
# https://taskfile.dev
version: '3'
vars:
DOCKERFILE: "{{.CONTEXT}}/build/Dockerfile"
DOCKERFILE: "build/Dockerfile"
tasks:
lint:
@@ -11,5 +12,4 @@ tasks:
- godolint {{.DOCKERFILE}}
build:
cmds:
- docker build --build-arg TARGETCMD=queryService -t {{.IMAGE_NAME}}_queryservice -f {{.DOCKERFILE}} {{.CONTEXT}}
- docker build --build-arg TARGETCMD=queryRunner -t {{.IMAGE_NAME}}_queryrunner -f {{.DOCKERFILE}} {{.CONTEXT}}
- docker build -t {{.IMAGE_NAME}} -f {{.DOCKERFILE}} .
+8 -3
View File
@@ -1,10 +1,11 @@
---
# https://taskfile.dev
version: '3'
vars:
PROTO_DIR: "{{.CONTEXT}}/serviceInterfaces"
API_DIR: "{{.CONTEXT}}/api"
PROTO_DIR: serviceInterfaces
API_DIR: api
tasks:
lint:
@@ -15,4 +16,8 @@ tasks:
- protolint lint -fix {{.PROTO_DIR}}
generate:
cmds:
- protoc --proto_path={{.PROTO_DIR}} --go_out={{.API_DIR}}/serviceInterfaces --go-grpc_out={{.API_DIR}} --go_opt=paths=source_relative --experimental_allow_proto3_optional main.proto
- |
protoc --proto_path={{.PROTO_DIR}} \
--go_out={{.API_DIR}}/serviceInterfaces --go-grpc_out={{.API_DIR}} \
--go_opt=paths=source_relative --experimental_allow_proto3_optional \
main.proto
+15 -8
View File
@@ -1,3 +1,4 @@
---
# https://taskfile.dev
version: '3'
@@ -8,25 +9,31 @@ includes:
internal: true
vars:
COVERAGE_FILE: "{{.CONTEXT}}/{{.OUT_DIR}}/coverage.out"
COVERAGE_FILE: "{{.OUT_DIR}}/coverage.out"
tasks:
mocks:
cmds:
- mockery
unit:
vars:
INTERNAL: "{{.CONTEXT}}/internal/..."
API: "{{.CONTEXT}}/api/..."
INTERNAL: "./internal/..."
API: "./api/..."
cmds:
- go test {{.INTERNAL}} {{.API}} -coverpkg={{.INTERNAL}},{{.API}} -coverprofile={{.COVERAGE_FILE}}
- |
go test {{.INTERNAL}} {{.API}} \
-coverpkg={{.INTERNAL}},{{.API}} -coverprofile={{.COVERAGE_FILE}}
unit:coverage:
deps:
- unit
vars:
TMP_FILE: "{{.CONTEXT}}/{{.OUT_DIR}}/coverage.tmp"
TMP_FILE: "{{.OUT_DIR}}/coverage.tmp"
cmds:
- go tool cover -func={{.COVERAGE_FILE}} > {{.TMP_FILE}}
- cat {{.TMP_FILE}}
- |
COVERAGE=$(grep total: {{.TMP_FILE}} | awk '{print $3}' | sed 's/%//' | bc)
COVERAGE=$(grep total: {{.TMP_FILE}} \
| awk '{print $3}' | sed 's/%//' | bc)
echo ""
echo "Coverage Threshold: {{.COVERAGE_THRESHOLD}}%"
echo "Total Coverage: $COVERAGE%"
@@ -44,7 +51,7 @@ tasks:
deps:
- docker:build
cmds:
- go test -count=1 -v {{.CONTEXT}}/test/...
- go test -count=1 -v test/...
integration:nobuild:
cmds:
- go test -v {{.CONTEXT}}/test/...
- go test -v test/...
+3 -2
View File
@@ -1,7 +1,8 @@
---
version: "2"
servers:
- engine: postgresql
uri: "postgres://${DB_USER}:${DB_PASS}@${DB_HOST}:${DB_PORT}/${DB_NAME}?sslmode=disable"
uri: "${DB_URI}"
sql:
- name: "db"
engine: "postgresql"
@@ -13,4 +14,4 @@ sql:
go:
package: "repository"
out: "internal/database/repository"
sql_package: "pgx/v5"
sql_package: "pgx/v5"
+24 -10
View File
@@ -26,17 +26,19 @@ func createAPIContainer(t *testing.T, ctx context.Context, config *apiContainerC
}
req := testcontainers.ContainerRequest{
Image: fmt.Sprintf("queryorchestration_%s:latest", config.ServiceName),
Image: "queryorchestration:latest",
Env: map[string]string{
"DB_USER": config.DB.User,
"DB_PASS": config.DB.Password,
"DB_HOST": config.DB.Host,
"DB_NAME": config.DB.Name,
"DB_PORT": strconv.Itoa(config.DB.Port),
"DB_USER": config.DB.User,
"DB_PASS": config.DB.Password,
"DB_HOST": config.DB.Host,
"DB_NAME": config.DB.Name,
"DB_PORT": strconv.Itoa(config.DB.Port),
"DB_NOSSL": "1",
},
ExposedPorts: []string{port.Port()},
WaitingFor: wait.ForListeningPort(port),
WaitingFor: wait.ForLog("Listening on port 8080"),
Networks: []string{config.Network.Name},
Entrypoint: []string{fmt.Sprintf("./bin/%s", config.ServiceName)},
}
container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
@@ -65,7 +67,10 @@ func createAPIContainer(t *testing.T, ctx context.Context, config *apiContainerC
}
return conn, func() {
container.Terminate(ctx)
err := container.Terminate(ctx)
if err != nil {
t.Error(err)
}
}
}
@@ -82,8 +87,17 @@ func createAPIDependencies(t *testing.T, ctx context.Context, serviceName string
return conn, func() {
testcontainers.CleanupNetwork(t, network)
dbContainer.Terminate(ctx)
err := dbContainer.Terminate(ctx)
if err != nil {
t.Error(err)
}
err = conn.Close()
if err != nil {
t.Error(err)
}
containerCleanup()
conn.Close()
}
}
+1 -1
View File
@@ -43,7 +43,7 @@ func createDB(t *testing.T, ctx context.Context, network *testcontainers.DockerN
}
req := testcontainers.ContainerRequest{
Image: "postgres:latest",
Image: "postgres:17.2-alpine3.21",
Env: map[string]string{
"POSTGRES_DB": config.Name,
"POSTGRES_USER": config.User,
+1 -1
View File
@@ -13,7 +13,7 @@ import (
func TestQueryRunner(t *testing.T) {
ctx := context.Background()
queue, cleanup := createQueueDependencies(t, ctx, "queryrunner")
queue, cleanup := createQueueDependencies(t, ctx, "queryRunner")
defer cleanup()
document := document.Document{
+1 -1
View File
@@ -11,7 +11,7 @@ import (
func TestQueryService(t *testing.T) {
ctx := context.Background()
conn, cleanup := createAPIDependencies(t, ctx, "queryservice")
conn, cleanup := createAPIDependencies(t, ctx, "queryService")
defer cleanup()
client := serviceinterfaces.NewQueryServiceClient(conn)
+21 -6
View File
@@ -32,7 +32,7 @@ func createQueueContainer(t *testing.T, ctx context.Context, config *queueContai
}
req := testcontainers.ContainerRequest{
Image: fmt.Sprintf("queryorchestration_%s:latest", config.ServiceName),
Image: "queryorchestration:latest",
Env: map[string]string{
"QUEUE_URL": config.Queue.URL,
"AWS_DEFAULT_REGION": config.Queue.Region,
@@ -46,8 +46,11 @@ func createQueueContainer(t *testing.T, ctx context.Context, config *queueContai
"DB_HOST": config.DB.Host,
"DB_NAME": config.DB.Name,
"DB_PORT": strconv.Itoa(config.DB.Port),
"DB_NOSSL": "1",
},
Networks: []string{config.Network.Name},
Networks: []string{config.Network.Name},
WaitingFor: wait.ForLog("Listening to queue"),
Entrypoint: []string{fmt.Sprintf("./bin/%s", config.ServiceName)},
}
container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
@@ -86,7 +89,7 @@ func createQueue(t *testing.T, ctx context.Context, network *testcontainers.Dock
}
req := testcontainers.ContainerRequest{
Image: "localstack/localstack:latest",
Image: "localstack/localstack:4.0.3",
Env: map[string]string{
"AWS_ACCESS_KEY_ID": provider.Value.AccessKeyID,
"AWS_SECRET_ACCESS_KEY": provider.Value.SecretAccessKey,
@@ -210,8 +213,20 @@ func createQueueDependencies(t *testing.T, ctx context.Context, serviceName stri
return queue, func() {
testcontainers.CleanupNetwork(t, network)
dbContainer.Terminate(ctx)
(*queue.Container).Terminate(ctx)
container.Terminate(ctx)
err := dbContainer.Terminate(ctx)
if err != nil {
t.Error(err)
}
err = (*queue.Container).Terminate(ctx)
if err != nil {
t.Error(err)
}
err = container.Terminate(ctx)
if err != nil {
t.Error(err)
}
}
}