diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cbb27523..6404e9a9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,111 +17,17 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true -jobs: - - test-postgresql-windows: - if: true # false to skip job during debug - name: Test on Windows - runs-on: windows-latest - steps: - - - name: Start PostgreSQL on Windows - run: | - $pgService = Get-Service -Name postgresql* - Set-Service -InputObject $pgService -Status running -StartupType automatic - Start-Process -FilePath "$env:PGBIN\pg_isready" -Wait -PassThru - - - name: Create scheduler user on Windows - run: | - & $env:PGBIN\psql --command="CREATE USER scheduler PASSWORD 'somestrong'" --command="\du" - - - name: Create timetable database - run: | - & $env:PGBIN\createdb --owner=scheduler timetable - $env:PGPASSWORD = 'somestrong' - & $env:PGBIN\psql --username=scheduler --host=localhost --list timetable - - - name: Check out code - uses: actions/checkout@v5 - - - name: Set up Golang - uses: actions/setup-go@v6 - with: - go-version: '1.25' - - - name: Test - run: go test -v -p 1 -parallel 1 -failfast ./... - - - - test-postgresql-macos: - if: true # false to skip job during debug - name: Test on MacOS - runs-on: macos-latest - steps: - - - name: Start PostgreSQL on MacOS - run: | - brew update - brew install postgresql@16 - brew link --force postgresql@16 - brew services start postgresql@16 - echo "Check PostgreSQL service is running" - i=10 - COMMAND='pg_isready' - while [ $i -gt 0 ]; do - echo "Check PostgreSQL service status" - eval $COMMAND && break - ((i--)) - if [ $i == 0 ]; then - echo "PostgreSQL service not ready, all attempts exhausted" - exit 1 - fi - echo "PostgreSQL service not ready, wait 10 more sec, attempts left: $i" - sleep 10 - done - - # Homebrew creates an account with the same name as the installing user, but no password - - name: Create scheduler user - run: | - psql --command="CREATE USER scheduler PASSWORD 'somestrong'" --command="\du" postgres - - - name: Create timetable database - run: | - createdb --owner=scheduler timetable - PGPASSWORD=somestrong psql --username=scheduler --host=localhost --list timetable +# This workflow uses testcontainers-go for PostgreSQL testing +# Docker provides consistent environment - only Ubuntu needed - - name: Check out code - uses: actions/checkout@v5 - - - name: Set up Golang - uses: actions/setup-go@v6 - with: - go-version: '1.25' - - - name: Test - run: go test -v -p 1 -parallel 1 -failfast ./... +jobs: - test-postgresql-ubuntu: + test: if: true # false to skip job during debug - name: Test and Build on Ubuntu + name: Test and Build with Testcontainers runs-on: ubuntu-latest steps: - - name: Start PostgreSQL on Ubuntu - run: | - sudo systemctl start postgresql.service - pg_isready - - - name: Create scheduler user - run: | - sudo -u postgres psql --command="CREATE USER scheduler PASSWORD 'somestrong'" --command="\du" - - - name: Create timetable database - run: | - sudo -u postgres createdb --owner=scheduler timetable - PGPASSWORD=somestrong psql --username=scheduler --host=localhost --list timetable - - name: Check out code uses: actions/checkout@v5 @@ -140,13 +46,15 @@ jobs: with: version: latest - - name: Test - run: go test -failfast -v -timeout=300s -p 1 -coverprofile=profile.cov ./... + - name: Test with Testcontainers + run: go test -failfast -v -timeout=300s -coverprofile=profile.cov ./... - - name: Coveralls - uses: shogo82148/actions-goveralls@v1 + - name: Upload coverage profile + uses: actions/upload-artifact@v4 with: - path-to-profile: profile.cov + name: coverage-profile-${{ github.run_id }} + path: profile.cov + retention-days: 1 - name: Run GoReleaser uses: goreleaser/goreleaser-action@v6 @@ -154,9 +62,26 @@ jobs: version: latest args: release --snapshot --skip=publish --clean + coverage: + if: true # false to skip job during debug + name: Coverage Report + runs-on: ubuntu-latest + needs: [test] + steps: + + - name: Download coverage profile + uses: actions/download-artifact@v4 + with: + name: coverage-profile-${{ github.run_id }} + + - name: Send coverage to Coveralls + uses: coverallsapp/github-action@v2 + with: + file: profile.cov + build-docs: if: true # false to skip job during debug - needs: [test-postgresql-ubuntu, test-postgresql-windows, test-postgresql-macos] + needs: [test] name: Build Docs runs-on: ubuntu-latest steps: @@ -201,7 +126,7 @@ jobs: test-docker-images: if: true # false to skip job during debug - needs: [test-postgresql-ubuntu, test-postgresql-windows, test-postgresql-macos] + needs: [test] name: Test Docker Image Build runs-on: ubuntu-latest steps: diff --git a/.vscode/settings.json b/.vscode/settings.json index 37441bee..b0e05012 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "files.eol": "\n" + "files.eol": "\n", + "terminal.integrated.defaultProfile.windows": "PowerShell" } \ No newline at end of file diff --git a/go.mod b/go.mod index 2adf33f9..b2a9b814 100644 --- a/go.mod +++ b/go.mod @@ -14,29 +14,76 @@ require ( github.com/sirupsen/logrus v1.9.3 github.com/spf13/viper v1.21.0 github.com/stretchr/testify v1.11.1 + github.com/testcontainers/testcontainers-go v0.39.0 + github.com/testcontainers/testcontainers-go/modules/postgres v0.39.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 ) require ( + dario.cat/mergo v1.0.2 // indirect + github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/errdefs/pkg v0.3.0 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/containerd/platforms v0.2.1 // indirect + github.com/cpuguy83/dockercfg v0.3.2 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/docker v28.4.0+incompatible // indirect + github.com/docker/go-connections v0.6.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/ebitengine/purego v0.9.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/klauspost/compress v1.18.0 // indirect + github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 // indirect + github.com/magiconair/properties v1.8.10 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/go-archive v0.1.0 // indirect + github.com/moby/patternmatcher v0.6.0 // indirect + github.com/moby/sys/sequential v0.6.0 // indirect + github.com/moby/sys/user v0.4.0 // indirect + github.com/moby/sys/userns v0.1.0 // indirect + github.com/moby/term v0.5.2 // indirect + github.com/morikuni/aec v1.0.0 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.1 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/sagikazarmark/locafero v0.12.0 // indirect + github.com/shirou/gopsutil/v4 v4.25.8 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spf13/cast v1.10.0 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/tklauser/go-sysconf v0.3.15 // indirect + github.com/tklauser/numcpus v0.10.0 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect + go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/otel/trace v1.38.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/crypto v0.42.0 // indirect golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.36.0 // indirect golang.org/x/text v0.29.0 // indirect + google.golang.org/grpc v1.75.0 // indirect + google.golang.org/protobuf v1.36.7 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index a773a2b4..96c61bd8 100644 --- a/go.sum +++ b/go.sum @@ -1,19 +1,65 @@ +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/cavaliercoder/grab v2.0.0+incompatible h1:wZHbBQx56+Yxjx2TCGDcenhh3cJn7cCLMfkEPmySTSE= github.com/cavaliercoder/grab v2.0.0+incompatible/go.mod h1:tTBkfNqSBfuMmMBFaO2phgyhdYhiZQ/+iXCZDzcDsMI= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= +github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= +github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= +github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/cybertec-postgresql/pgx-migrator v1.2.0 h1:e96gr058i/yCoJZCXGUUZ7cRD+d9O7ttUygZlFzFrlE= github.com/cybertec-postgresql/pgx-migrator v1.2.0/go.mod h1:g9qBzWOnxlgFa0JW5ujWfWgRhko4YBk03w/QIxuFJ1Q= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v28.4.0+incompatible h1:KVC7bz5zJY/4AZe/78BIvCnPsLaC9T/zh72xnlrTTOk= +github.com/docker/docker v28.4.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= +github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/ebitengine/purego v0.9.0 h1:mh0zpKBIXDceC63hpvPuGLiJ8ZAa3DfrFTudmfi8A4k= +github.com/ebitengine/purego v0.9.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= @@ -24,10 +70,42 @@ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jessevdk/go-flags v1.6.1 h1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bBK4= github.com/jessevdk/go-flags v1.6.1/go.mod h1:Mk8T1hIAWpOiJiHa9rJASDK2UGWji0EuPGBnNLMooyc= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 h1:mFWunSatvkQQDhpdyuFAYwyAan3hzCuma+Pz8sqvOfg= +github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= +github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= +github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mdelapenya/tlscert v0.2.0 h1:7H81W6Z/4weDvZBNOfQte5GpIMo0lGYEeWbkGp5LJHI= +github.com/mdelapenya/tlscert v0.2.0/go.mod h1:O4njj3ELLnJjGdkN7M/vIVCpZ+Cf0L6muqOG4tLSl8o= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ= +github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo= +github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= +github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= +github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= +github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= +github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= +github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= +github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= +github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/ory/mail/v3 v3.0.1-0.20210418065910-7f033ddea8dc h1:BU12v9x5hvONtYU2R2LnlkxmWSsjzco046NzJLcWMHg= github.com/ory/mail/v3 v3.0.1-0.20210418065910-7f033ddea8dc/go.mod h1:vAPEMm1zIQKGmM9hcZTSlOU/CDVCXHGOw6SFxPlSoHw= github.com/pashagolub/pgxmock/v3 v3.4.0 h1:87VMr2q7m2+6VzXo4Tsp9kMklGlj6mMN19Hp/bp2Rwo= @@ -41,14 +119,18 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 h1:mZHayPoR0lNmnHyvtYjDeq0zlVHn9K/ZXoy17ylucdo= github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5/go.mod h1:GEXHk5HgEKCvEIIrSpFI3ozzG5xOKA2DVlEX/gGnewM= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4= github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI= github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE= github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= +github.com/shirou/gopsutil/v4 v4.25.8 h1:NnAsw9lN7587WHxjJA9ryDnqhJpFH6A+wagYWTOH970= +github.com/shirou/gopsutil/v4 v4.25.8/go.mod h1:q9QdMmfAOVIw7a+eF86P7ISEU6ka+NLgkUxlopV4RwI= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= @@ -60,6 +142,8 @@ github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3A github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -67,17 +151,65 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/testcontainers/testcontainers-go v0.39.0 h1:uCUJ5tA+fcxbFAB0uP3pIK3EJ2IjjDUHFSZ1H1UxAts= +github.com/testcontainers/testcontainers-go v0.39.0/go.mod h1:qmHpkG7H5uPf/EvOORKvS6EuDkBUPE3zpVGaH9NL7f8= +github.com/testcontainers/testcontainers-go/modules/postgres v0.39.0 h1:REJz+XwNpGC/dCgTfYvM4SKqobNqDBfvhq74s2oHTUM= +github.com/testcontainers/testcontainers-go/modules/postgres v0.39.0/go.mod h1:4K2OhtHEeT+JSIFX4V8DkGKsyLa96Y2vLdd3xsxD5HE= +github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= +github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= +github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= +github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= +golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c h1:AtEkQdl5b6zsybXcbz00j1LwNodDuH6hVifIaNqk7NQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo= +google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= +google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= +google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= +google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -89,3 +221,5 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= diff --git a/internal/api/api_test.go b/internal/api/api_test.go index 41467567..82370a6f 100644 --- a/internal/api/api_test.go +++ b/internal/api/api_test.go @@ -30,7 +30,7 @@ func (r *apihandler) StopChain(context.Context, int) error { return nil } -var restsrv = Init(config.RestAPIOpts{Port: 8080}, log.Init(config.LoggingOpts{LogLevel: "error"})) +var restsrv = Init(config.RestAPIOpts{Port: 8080}, log.Init(config.LoggingOpts{LogLevel: "panic"})) const turl = "http://localhost:8080/" diff --git a/internal/pgengine/copy_test.go b/internal/pgengine/copy_test.go index 0e27ae22..c551bf84 100644 --- a/internal/pgengine/copy_test.go +++ b/internal/pgengine/copy_test.go @@ -6,13 +6,15 @@ import ( "testing" "github.com/cybertec-postgresql/pg_timetable/internal/pgengine" + "github.com/cybertec-postgresql/pg_timetable/internal/testutils" "github.com/stretchr/testify/assert" ) func TestCopyFromFile(t *testing.T) { - teardownTestCase := SetupTestCase(t) - defer teardownTestCase(t) + container, cleanup := testutils.SetupPostgresContainer(t) + defer cleanup() ctx := context.Background() + pge := container.Engine _, err := pge.CopyFromFile(ctx, "fake.csv", "COPY location FROM STDIN") assert.Error(t, err, "Should fail for missing file") _, err = pge.ConfigDb.Exec(ctx, "CREATE UNLOGGED TABLE csv_test(id integer, val text)") @@ -29,9 +31,10 @@ func TestCopyFromFile(t *testing.T) { } func TestCopyToFile(t *testing.T) { - teardownTestCase := SetupTestCase(t) - defer teardownTestCase(t) + container, cleanup := testutils.SetupPostgresContainer(t) + defer cleanup() ctx := context.Background() + pge := container.Engine _, err := pge.CopyToFile(ctx, "", "COPY location TO STDOUT") assert.Error(t, err, "Should fail for empty file name") cnt, err := pge.CopyToFile(ctx, "test.csv", "COPY (SELECT generate_series(1,5)) TO STDOUT (FORMAT csv)") diff --git a/internal/pgengine/migration_test.go b/internal/pgengine/migration_test.go index dab01126..1511d36a 100644 --- a/internal/pgengine/migration_test.go +++ b/internal/pgengine/migration_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/cybertec-postgresql/pg_timetable/internal/pgengine" + "github.com/cybertec-postgresql/pg_timetable/internal/testutils" migrator "github.com/cybertec-postgresql/pgx-migrator" "github.com/stretchr/testify/assert" ) @@ -14,10 +15,11 @@ import ( var initialsql string func TestMigrations(t *testing.T) { - teardownTestCase := SetupTestCase(t) - defer teardownTestCase(t) + container, cleanup := testutils.SetupPostgresContainer(t) + defer cleanup() ctx := context.Background() + pge := container.Engine _, err := pge.ConfigDb.Exec(ctx, "DROP SCHEMA IF EXISTS timetable CASCADE") assert.NoError(t, err) _, err = pge.ConfigDb.Exec(ctx, string(initialsql)) @@ -38,13 +40,14 @@ func TestExecuteMigrationScript(t *testing.T) { } func TestInitMigrator(t *testing.T) { - teardownTestCase := SetupTestCase(t) - defer teardownTestCase(t) + container, cleanup := testutils.SetupPostgresContainer(t) + defer cleanup() pgengine.Migrations = func() migrator.Option { return migrator.Migrations() } ctx := context.Background() + pge := container.Engine err := pge.MigrateDb(ctx) assert.Error(t, err, "Empty migrations") _, err = pge.CheckNeedMigrateDb(ctx) diff --git a/internal/pgengine/notification_test.go b/internal/pgengine/notification_test.go index aa7417ca..f4325481 100644 --- a/internal/pgengine/notification_test.go +++ b/internal/pgengine/notification_test.go @@ -8,11 +8,12 @@ import ( "github.com/cybertec-postgresql/pg_timetable/internal/config" "github.com/cybertec-postgresql/pg_timetable/internal/pgengine" + "github.com/cybertec-postgresql/pg_timetable/internal/testutils" "github.com/stretchr/testify/assert" ) // notify sends NOTIFY each second until context is available -func notifyAndCheck(ctx context.Context, conn pgengine.PgxIface, t *testing.T, channel string) { +func notifyAndCheck(ctx context.Context, conn pgengine.PgxIface, pge *pgengine.PgEngine, t *testing.T, channel string) { vals := []string{ `{"ConfigID": 1, "Command": "STOP", "Ts": 1}`, //{1, "STOP", 1, 0}, `{"ConfigID": 2, "Command": "START", "Ts": 1}`, //{2, "START", 1, 0}, @@ -42,12 +43,13 @@ func notifyAndCheck(ctx context.Context, conn pgengine.PgxIface, t *testing.T, c } func TestHandleNotifications(t *testing.T) { - teardownTestCase := SetupTestCaseEx(t, func(c *config.CmdOptions) { + container, cleanup := testutils.SetupPostgresContainerWithOptions(t, func(c *config.CmdOptions) { c.Start.Debug = true }) - defer teardownTestCase(t) + defer cleanup() ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() + pge := container.Engine go pge.HandleNotifications(ctx) time.Sleep(5 * time.Second) conn, err := pge.ConfigDb.Acquire(ctx) // HandleNotifications() uses blocking manner, so we want another connection @@ -55,5 +57,5 @@ func TestHandleNotifications(t *testing.T) { defer conn.Release() _, err = conn.Exec(ctx, "UNLISTEN *") // do not interfere with the main handler assert.NoError(t, err) - notifyAndCheck(ctx, pge.ConfigDb, t, "pgengine_unit_test") + notifyAndCheck(ctx, pge.ConfigDb, pge, t, pge.ClientName) } diff --git a/internal/pgengine/pgengine_test.go b/internal/pgengine/pgengine_test.go index 2aa26640..670af9f2 100644 --- a/internal/pgengine/pgengine_test.go +++ b/internal/pgengine/pgengine_test.go @@ -15,45 +15,15 @@ import ( "github.com/cybertec-postgresql/pg_timetable/internal/log" "github.com/cybertec-postgresql/pg_timetable/internal/pgengine" "github.com/cybertec-postgresql/pg_timetable/internal/scheduler" + "github.com/cybertec-postgresql/pg_timetable/internal/testutils" ) -// this instance used for all engine tests -var pge *pgengine.PgEngine - -var cmdOpts *config.CmdOptions = config.NewCmdOptions("--clientname=pgengine_unit_test", "--connstr=postgresql://scheduler:somestrong@localhost/timetable") - -// SetupTestCaseEx allows to configure the test case before execution -func SetupTestCaseEx(t *testing.T, fc func(c *config.CmdOptions)) func(t *testing.T) { - fc(cmdOpts) - return SetupTestCase(t) -} - -// SetupTestCase used to connect and to initialize test PostgreSQL database -func SetupTestCase(t *testing.T) func(t *testing.T) { - t.Helper() - timeout := time.After(30 * time.Second) - done := make(chan bool) - go func() { - pge, _ = pgengine.New(context.Background(), *cmdOpts, log.Init(config.LoggingOpts{LogLevel: "error"})) - done <- true - }() - select { - case <-timeout: - t.Fatal("Cannot connect and initialize test database in time") - case <-done: - } - return func(t *testing.T) { - _, _ = pge.ConfigDb.Exec(context.Background(), "DROP SCHEMA IF EXISTS timetable CASCADE") - pge.ConfigDb.Close() - t.Log("Test schema dropped") - } -} - func TestInitAndTestConfigDBConnection(t *testing.T) { - teardownTestCase := SetupTestCase(t) - defer teardownTestCase(t) + container, cleanup := testutils.SetupPostgresContainer(t) + defer cleanup() ctx := context.Background() + pge := container.Engine require.NotNil(t, pge.ConfigDb, "ConfigDB should be initialized") @@ -105,8 +75,10 @@ func TestInitAndTestConfigDBConnection(t *testing.T) { t.Run("Check connection closing", func(t *testing.T) { pge.Finalize() assert.Nil(t, pge.ConfigDb, "Connection isn't closed properly") - // reinit connection to execute teardown actions - pge, _ = pgengine.New(context.Background(), *cmdOpts, log.Init(config.LoggingOpts{LogLevel: "error"})) + // reinit connection to execute teardown actions - create new container + newContainer, newCleanup := testutils.SetupPostgresContainer(t) + defer newCleanup() + pge = newContainer.Engine }) } @@ -115,15 +87,16 @@ func TestFailedConnect(t *testing.T) { c := config.NewCmdOptions("--connstr='host=fake'", "-c", "pgengine_test") ctx, cancel := context.WithTimeout(context.Background(), pgengine.WaitTime*2) defer cancel() - _, err := pgengine.New(ctx, *c, log.Init(config.LoggingOpts{LogLevel: "error"})) + _, err := pgengine.New(ctx, *c, log.Init(config.LoggingOpts{LogLevel: "panic", LogDBLevel: "none"})) assert.ErrorIs(t, err, ctx.Err()) } func TestSchedulerFunctions(t *testing.T) { - teardownTestCase := SetupTestCase(t) - defer teardownTestCase(t) + container, cleanup := testutils.SetupPostgresContainer(t) + defer cleanup() ctx := context.Background() + pge := container.Engine t.Run("Check DeleteChainConfig function", func(t *testing.T) { assert.Equal(t, false, pge.DeleteChain(ctx, 0), "Should not delete in clean database") @@ -180,10 +153,11 @@ func TestSchedulerFunctions(t *testing.T) { } func TestGetRemoteDBTransaction(t *testing.T) { - teardownTestCase := SetupTestCase(t) - defer teardownTestCase(t) + container, cleanup := testutils.SetupPostgresContainer(t) + defer cleanup() ctx := context.Background() - remoteDb, err := pge.GetRemoteDBConnection(context.Background(), cmdOpts.ConnStr) + pge := container.Engine + remoteDb, err := pge.GetRemoteDBConnection(context.Background(), container.ConnStr) defer pge.FinalizeDBConnection(ctx, remoteDb) require.NoError(t, err, "remoteDB should be initialized") require.NotNil(t, remoteDb, "remoteDB should be initialized") @@ -196,12 +170,13 @@ func TestGetRemoteDBTransaction(t *testing.T) { } func TestSamplesScripts(t *testing.T) { - teardownTestCase := SetupTestCase(t) - defer teardownTestCase(t) + container, cleanup := testutils.SetupPostgresContainer(t) + defer cleanup() files, err := os.ReadDir("../../samples") assert.NoError(t, err, "Cannot read samples directory") - l := log.Init(config.LoggingOpts{LogLevel: "error"}) + l := log.Init(config.LoggingOpts{LogLevel: "panic", LogDBLevel: "none"}) + pge := container.Engine for _, f := range files { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() diff --git a/internal/scheduler/chain_test.go b/internal/scheduler/chain_test.go index d080fe41..8524b481 100644 --- a/internal/scheduler/chain_test.go +++ b/internal/scheduler/chain_test.go @@ -29,7 +29,7 @@ func TestAsyncChains(t *testing.T) { mock, err := pgxmock.NewPool() assert.NoError(t, err) pge := pgengine.NewDB(mock, "scheduler_unit_test") - sch := New(pge, log.Init(config.LoggingOpts{LogLevel: "error"})) + sch := New(pge, log.Init(config.LoggingOpts{LogLevel: "panic", LogDBLevel: "none"})) n1 := &pgconn.Notification{Payload: `{"ConfigID": 1, "Command": "START"}`} n2 := &pgconn.Notification{Payload: `{"ConfigID": 2, "Command": "START"}`} ns := &pgconn.Notification{Payload: `{"ConfigID": 24, "Command": "STOP"}`} @@ -60,7 +60,7 @@ func TestChainWorker(t *testing.T) { mock, err := pgxmock.NewPool() assert.NoError(t, err) pge := pgengine.NewDB(mock, "-c", "scheduler_unit_test", "--password=somestrong") - sch := New(pge, log.Init(config.LoggingOpts{LogLevel: "error"})) + sch := New(pge, log.Init(config.LoggingOpts{LogLevel: "panic"})) chains := make(chan Chain, 16) t.Run("Check chainWorker if context cancelled", func(*testing.T) { @@ -98,7 +98,7 @@ func TestExecuteChain(t *testing.T) { mock, err := pgxmock.NewPool() assert.NoError(t, err) pge := pgengine.NewDB(mock, "-c", "scheduler_unit_test", "--password=somestrong") - sch := New(pge, log.Init(config.LoggingOpts{LogLevel: "error"})) + sch := New(pge, log.Init(config.LoggingOpts{LogLevel: "panic", LogDBLevel: "none"})) ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -109,7 +109,7 @@ func TestExecuteChainElement(t *testing.T) { mock, err := pgxmock.NewPool() assert.NoError(t, err) pge := pgengine.NewDB(mock, "-c", "scheduler_unit_test", "--password=somestrong") - sch := New(pge, log.Init(config.LoggingOpts{LogLevel: "error"})) + sch := New(pge, log.Init(config.LoggingOpts{LogLevel: "panic", LogDBLevel: "none"})) ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -122,7 +122,7 @@ func TestExecuteOnErrorHandler(t *testing.T) { mock, err := pgxmock.NewPool() assert.NoError(t, err) pge := pgengine.NewDB(mock, "-c", "scheduler_unit_test", "--password=somestrong") - sch := New(pge, log.Init(config.LoggingOpts{LogLevel: "error"})) + sch := New(pge, log.Init(config.LoggingOpts{LogLevel: "panic", LogDBLevel: "none"})) t.Run("check error handler if everything is fine", func(t *testing.T) { mock.ExpectExec("FOO").WillReturnResult(pgxmock.NewResult("FOO", 1)) diff --git a/internal/scheduler/interval_chain_test.go b/internal/scheduler/interval_chain_test.go index 50d44d89..3c7aa365 100644 --- a/internal/scheduler/interval_chain_test.go +++ b/internal/scheduler/interval_chain_test.go @@ -15,7 +15,7 @@ func TestIntervalChain(t *testing.T) { mock, err := pgxmock.NewPool() assert.NoError(t, err) pge := pgengine.NewDB(mock, "scheduler_unit_test") - sch := New(pge, log.Init(config.LoggingOpts{LogLevel: "error"})) + sch := New(pge, log.Init(config.LoggingOpts{LogLevel: "panic", LogDBLevel: "none"})) ichain := IntervalChain{Interval: 42} assert.True(t, ichain.IsListed([]IntervalChain{ichain})) diff --git a/internal/scheduler/scheduler_test.go b/internal/scheduler/scheduler_test.go index 56b4fc52..6c351c8a 100644 --- a/internal/scheduler/scheduler_test.go +++ b/internal/scheduler/scheduler_test.go @@ -10,36 +10,14 @@ import ( "github.com/cybertec-postgresql/pg_timetable/internal/config" "github.com/cybertec-postgresql/pg_timetable/internal/log" - "github.com/cybertec-postgresql/pg_timetable/internal/pgengine" + "github.com/cybertec-postgresql/pg_timetable/internal/testutils" ) -var pge *pgengine.PgEngine - -// SetupTestCase used to connect and to initialize test PostgreSQL database -func SetupTestCase(t *testing.T) func(t *testing.T) { - cmdOpts := config.NewCmdOptions("-c", "pgengine_unit_test", "--connstr=postgresql://scheduler:somestrong@localhost/timetable") - t.Log("Setup test case") - timeout := time.After(6 * time.Second) - done := make(chan bool) - go func() { - pge, _ = pgengine.New(context.Background(), *cmdOpts, log.Init(config.LoggingOpts{LogLevel: "error"})) - done <- true - }() - select { - case <-timeout: - t.Fatal("Cannot connect and initialize test database in time") - case <-done: - } - return func(t *testing.T) { - _, _ = pge.ConfigDb.Exec(context.Background(), "DROP SCHEMA IF EXISTS timetable CASCADE") - t.Log("Test schema dropped") - } -} - func TestRun(t *testing.T) { - teardownTestCase := SetupTestCase(t) - defer teardownTestCase(t) + container, cleanup := testutils.SetupPostgresContainer(t) + defer cleanup() + pge := container.Engine require.NotNil(t, pge.ConfigDb, "ConfigDB should be initialized") err := pge.ExecuteCustomScripts(context.Background(), "../../samples/Exclusive.sql") @@ -56,7 +34,7 @@ func TestRun(t *testing.T) { assert.NoError(t, err, "Creating program tasks failed") err = pge.ExecuteCustomScripts(context.Background(), "../../samples/ManyTasks.sql") assert.NoError(t, err, "Creating many tasks failed") - sch := New(pge, log.Init(config.LoggingOpts{LogLevel: "error"})) + sch := New(pge, log.Init(config.LoggingOpts{LogLevel: "panic", LogDBLevel: "none"})) assert.NoError(t, sch.StartChain(context.Background(), 1)) assert.ErrorContains(t, sch.StopChain(context.Background(), -1), "No running chain found") go func() { diff --git a/internal/scheduler/shell_test.go b/internal/scheduler/shell_test.go index c2d8c454..492ee575 100644 --- a/internal/scheduler/shell_test.go +++ b/internal/scheduler/shell_test.go @@ -35,7 +35,7 @@ func TestShellCommand(t *testing.T) { mock, err := pgxmock.NewPool() // assert.NoError(t, err) pge := pgengine.NewDB(mock, "scheduler_unit_test") - scheduler := scheduler.New(pge, log.Init(config.LoggingOpts{LogLevel: "error"})) + scheduler := scheduler.New(pge, log.Init(config.LoggingOpts{LogLevel: "panic", LogDBLevel: "none"})) ctx := context.Background() _, _, err = scheduler.ExecuteProgramCommand(ctx, "", []string{""}) diff --git a/internal/scheduler/tasks_test.go b/internal/scheduler/tasks_test.go index 94ac8ada..9be87841 100644 --- a/internal/scheduler/tasks_test.go +++ b/internal/scheduler/tasks_test.go @@ -15,7 +15,7 @@ func TestExecuteTask(t *testing.T) { mock, err := pgxmock.NewPool() // assert.NoError(t, err) pge := pgengine.NewDB(mock, "scheduler_unit_test") - mocksch := New(pge, log.Init(config.LoggingOpts{LogLevel: "error"})) + mocksch := New(pge, log.Init(config.LoggingOpts{LogLevel: "panic", LogDBLevel: "none"})) et := func(task string, params []string) (err error) { _, err = mocksch.executeBuiltinTask(context.TODO(), task, params) diff --git a/internal/testutils/testcontainers.go b/internal/testutils/testcontainers.go new file mode 100644 index 00000000..7aaf066e --- /dev/null +++ b/internal/testutils/testcontainers.go @@ -0,0 +1,92 @@ +package testutils + +import ( + "context" + "testing" + "time" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/modules/postgres" + "github.com/testcontainers/testcontainers-go/wait" + + "github.com/cybertec-postgresql/pg_timetable/internal/config" + "github.com/cybertec-postgresql/pg_timetable/internal/log" + "github.com/cybertec-postgresql/pg_timetable/internal/pgengine" +) + +// PostgresTestContainer wraps the postgres container with pg_timetable engine +type PostgresTestContainer struct { + Container *postgres.PostgresContainer + Engine *pgengine.PgEngine + ConnStr string +} + +// SetupPostgresContainer creates a new PostgreSQL container for testing +func SetupPostgresContainer(t *testing.T) (*PostgresTestContainer, func()) { + t.Helper() + return SetupPostgresContainerWithOptions(t, nil) +} + +// SetupPostgresContainerWithOptions creates a PostgreSQL container with custom options +func SetupPostgresContainerWithOptions(t *testing.T, customizer func(*config.CmdOptions)) (*PostgresTestContainer, func()) { + t.Helper() + + ctx := context.Background() + + postgresContainer, err := postgres.Run(ctx, + "postgres:18-alpine", + postgres.WithDatabase("timetable"), + postgres.WithUsername("scheduler"), + postgres.WithPassword("somestrong"), + testcontainers.WithWaitStrategyAndDeadline( + 60*time.Second, + wait.ForLog("database system is ready to accept connections").WithOccurrence(2), + ), + ) + if err != nil { + t.Fatalf("Failed to start PostgreSQL container: %v", err) + } + + connStr, err := postgresContainer.ConnectionString(ctx, "sslmode=disable") + if err != nil { + t.Fatalf("Failed to get connection string: %v", err) + } + + cmdOpts := config.NewCmdOptions("--clientname=testcontainers_unit_test", "--connstr="+connStr) + + if customizer != nil { + customizer(cmdOpts) + } + + var pge *pgengine.PgEngine + timeout := time.After(3 * time.Minute) + done := make(chan bool) + go func() { + pge, err = pgengine.New(ctx, *cmdOpts, log.Init(config.LoggingOpts{LogLevel: "panic", LogDBLevel: "none"})) + done <- true + }() + + select { + case <-timeout: + t.Fatal("Cannot connect and initialize test database in time") + case <-done: + if err != nil { + t.Fatalf("Failed to initialize pg_timetable engine: %v", err) + } + } + + testContainer := &PostgresTestContainer{ + Container: postgresContainer, + Engine: pge, + ConnStr: connStr, + } + + cleanup := func() { + if err := postgresContainer.Terminate(ctx); err != nil { + t.Logf("Failed to terminate PostgreSQL container: %v", err) + } + t.Log("PostgreSQL test container terminated") + } + + return testContainer, cleanup +}