feat(testing): add full integration test suite
Some checks failed
Integration Tests / integration-test (push) Failing after -23m59s

This commit introduces a comprehensive integration test suite for the pgsql-broker.

The test suite includes:
- A Docker/Podman environment for running a PostgreSQL database, managed via a .
- Integration tests that cover the broker's lifecycle, including job creation, execution, and instance management.
- A GitHub Actions workflow to automate the execution of all tests on push and pull requests.
- A dedicated test configuration file () and helper test files.

refactor(worker): fix job processing transaction
- The worker's job processing now uses a single transaction to fetch and run a job, resolving a race condition where jobs were not in the 'running' state when being executed.
- The broker's database instance registration is now more robust, handling cases where another instance is already active.

The Makefile has been significantly updated to orchestrate the entire test flow, including setting up the database, starting/stopping the broker, and running unit and integration tests separately.
This commit is contained in:
2026-01-02 23:08:17 +02:00
parent 19e469ff54
commit 3e64f7ae2a
16 changed files with 406 additions and 34 deletions

View File

@@ -2,6 +2,7 @@ package worker
import (
"context"
"database/sql" // Import sql package
"fmt"
"sync"
"time"
@@ -145,50 +146,58 @@ func (w *Worker) processLoop(ctx context.Context) {
}
}
// processJobs processes jobs from the queue
// processJobs processes jobs from the queue within a transaction
func (w *Worker) processJobs(ctx context.Context, specificJob *models.Job) {
defer w.recoverPanic()
for i := 0; i < w.fetchSize; i++ {
tx, err := w.db.Begin(ctx) // Start transaction
if err != nil {
w.logger.Error("failed to begin transaction", "error", err)
return
}
var jobID int64
if specificJob != nil && specificJob.ID > 0 {
jobID = specificJob.ID
specificJob = nil // Only process once
} else {
// Fetch next job from database
var err error
jobID, err = w.fetchNextJob(ctx)
jobID, err = w.fetchNextJobTx(ctx, tx) // Use transaction
if err != nil {
tx.Rollback() // Rollback on fetch error
w.logger.Error("failed to fetch job", "error", err)
return
}
}
if jobID <= 0 {
// No more jobs
return
tx.Rollback() // No job found, rollback
return // No more jobs
}
// Run the job
if err := w.runJob(ctx, jobID); err != nil {
if err := w.runJobTx(ctx, tx, jobID); err != nil { // Use transaction
tx.Rollback() // Rollback on job execution error
w.logger.Error("failed to run job", "job_id", jobID, "error", err)
} else {
tx.Commit() // Commit if job successful
w.jobsHandled++
}
}
}
// fetchNextJob fetches the next job from the queue
func (w *Worker) fetchNextJob(ctx context.Context) (int64, error) {
// fetchNextJobTx fetches the next job from the queue within a transaction
func (w *Worker) fetchNextJobTx(ctx context.Context, tx adapter.DBTransaction) (int64, error) {
var retval int
var errmsg string
var jobID int64
var nullableJobID sql.NullInt64
err := w.db.QueryRow(ctx,
err := tx.QueryRow(ctx,
"SELECT p_retval, p_errmsg, p_job_id FROM broker_get($1, $2)",
w.QueueNumber, w.InstanceID,
).Scan(&retval, &errmsg, &jobID)
).Scan(&retval, &errmsg, &nullableJobID)
if err != nil {
return 0, fmt.Errorf("query error: %w", err)
@@ -198,17 +207,21 @@ func (w *Worker) fetchNextJob(ctx context.Context) (int64, error) {
return 0, fmt.Errorf("broker_get error: %s", errmsg)
}
return jobID, nil
if !nullableJobID.Valid {
return 0, nil
}
return nullableJobID.Int64, nil
}
// runJob executes a job
func (w *Worker) runJob(ctx context.Context, jobID int64) error {
// runJobTx executes a job within a transaction
func (w *Worker) runJobTx(ctx context.Context, tx adapter.DBTransaction, jobID int64) error {
w.logger.Debug("running job", "job_id", jobID)
var retval int
var errmsg string
err := w.db.QueryRow(ctx,
err := tx.QueryRow(ctx,
"SELECT p_retval, p_errmsg FROM broker_run($1)",
jobID,
).Scan(&retval, &errmsg)