10 Commits

Author SHA1 Message Date
65deb2e55c fix(cache): resolve test failures in Gitea Actions by closing Redis clients and using IPv4 address
Some checks failed
CI / test (push) Failing after 1m15s
CI / build (push) Has been skipped
CI / docker (push) Has been skipped
CI / lint (push) Successful in 13s
2026-08-20 18:12:09 +03:00
ca8b137e55 fmt: apply gofmt to test files
Some checks failed
CI / lint (push) Successful in 14s
CI / test (push) Failing after 1m13s
CI / build (push) Has been skipped
CI / docker (push) Has been skipped
2026-08-20 18:04:23 +03:00
6bebfa86fe fix(gitignore): make api ignore pattern root-specific
Some checks failed
CI / lint (push) Failing after 11s
CI / test (push) Has been skipped
CI / build (push) Has been skipped
CI / docker (push) Has been skipped
fix(test): add missing os import to test files
chore(deps): update dependencies from go mod tidy
2026-08-20 18:02:46 +03:00
900db58043 fix(test): use REDIS_ADDR environment variable in cron test mocks to match CI service hostname
Some checks failed
CI / lint (push) Failing after 11s
CI / test (push) Has been skipped
CI / build (push) Has been skipped
CI / docker (push) Has been skipped
2026-08-20 17:56:05 +03:00
e73ed82b7e fix(test): use REDIS_ADDR environment variable in test mocks to match CI service hostname
Some checks failed
CI / lint (push) Failing after 12s
CI / build (push) Has been skipped
CI / test (push) Has been skipped
CI / docker (push) Has been skipped
2026-08-20 17:49:04 +03:00
dbafaf57ce fix(ci): use Redis service hostname instead of localhost to avoid IPv6 connection issues
Some checks failed
CI / docker (push) Has been skipped
CI / lint (push) Successful in 13s
CI / test (push) Failing after 1m15s
CI / build (push) Has been skipped
2026-08-20 17:44:35 +03:00
6396f4e6ab fix(ci): add Redis service to test and build jobs to resolve connection refused errors
Some checks failed
CI / lint (push) Successful in 14s
CI / test (push) Failing after 1m48s
CI / build (push) Has been skipped
CI / docker (push) Has been skipped
2026-08-20 17:40:37 +03:00
904c0c35ce fix(ci): replace single-job workflow with proper 4-job Gitea Actions pipeline (lint → test → build → build → docker)
Some checks failed
CI / lint (push) Successful in 54s
CI / test (push) Failing after 1m13s
CI / build (push) Has been skipped
CI / docker (push) Has been skipped
2026-08-20 17:36:20 +03:00
70768d420e fix(ci): update Go version to 1.26.4 and modernize GitHub Actions
Some checks failed
/ build-and-deploy (push) Failing after 2m5s
2026-08-20 17:26:18 +03:00
b08e7b1b00 Merge branch 'full-implementation' into master
Some checks failed
/ build-and-deploy (push) Failing after 4m59s
2026-08-19 15:12:55 +03:00
7 changed files with 251 additions and 58 deletions

View File

@@ -1,51 +1,159 @@
name: CI
# Required repo secret: PACKAGES_TOKEN
# Generate at: Gitea → User Settings → Applications → Generate New Token
# Scopes: read:package, write:package
# Save as: Repository → Settings → Actions → Secrets → PACKAGES_TOKEN
# Note: GITHUB_TOKEN is read-only for packages in Gitea (issue #23642).
on:
push:
branches:
- master
branches: ["**"]
pull_request:
branches: ["master"]
env:
GO_VERSION: "1.26.4"
jobs:
build-and-deploy:
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
- uses: actions/setup-go@v5
with:
go-version: '1.22'
go-version: ${{ env.GO_VERSION }}
- name: Go Modules Cache
uses: actions/cache@v3
- name: Cache Go modules
uses: actions/cache@v4
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
~/.cache/go-build
key: go-lint-${{ runner.os }}-${{ hashFiles('go.sum') }}
restore-keys: |
${{ runner.os }}-go-
go-lint-${{ runner.os }}-
- name: Lint & Test
- name: Download Go modules
run: go mod download
- name: go vet
run: go vet ./...
- name: gofmt check
run: |
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.54.2
export PATH=$(go env GOPATH)/bin:$PATH
golangci-lint run ./...
go test -v -race ./...
fmt=$(gofmt -l .)
if [ -n "$fmt" ]; then
echo "Files needing formatting:"
echo "$fmt"
exit 1
fi
test:
runs-on: ubuntu-latest
services:
redis:
image: redis:7-alpine
ports:
- 6379:6379
# Set health checks to wait until redis is ready
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
needs: lint
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
- name: Cache Go modules and build cache
uses: actions/cache@v4
with:
path: |
~/go/pkg/mod
~/.cache/go-build
key: go-test-${{ runner.os }}-${{ hashFiles('go.sum') }}
restore-keys: |
go-test-${{ runner.os }}-
- name: Download Go modules
run: go mod download
- name: Run tests
env:
REDIS_ADDR: redis:6379
run: go test ./... -v -race -cover
build:
runs-on: ubuntu-latest
services:
redis:
image: redis:7-alpine
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
needs: test
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
- name: Cache Go modules and build cache
uses: actions/cache@v4
with:
path: |
~/go/pkg/mod
~/.cache/go-build
key: go-build-${{ runner.os }}-${{ hashFiles('go.sum') }}
restore-keys: |
go-build-${{ runner.os }}-
- name: Download Go modules
run: go mod download
- name: Build binary
env:
REDIS_ADDR: redis:6379
run: go build -o api ./cmd/api
docker:
runs-on: ubuntu-latest
needs: build
if: github.ref_name == 'master'
permissions:
packages: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
- name: Login to Docker Registry
uses: docker/login-action@v2
- name: Login to Gitea Registry
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
registry: gitea.mrixs.me
username: ${{ gitea.repository_owner }}
password: ${{ secrets.PACKAGES_TOKEN }}
- name: Build and Push Docker Image
uses: docker/build-push-action@v4
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
${{ secrets.DOCKER_USERNAME }}/trip-planner:latest
${{ secrets.DOCKER_USERNAME }}/trip-planner:${{ github.sha }}
gitea.mrixs.me/mrixs/trip-planner:latest
gitea.mrixs.me/mrixs/trip-planner:${{ github.sha }}
cache-from: type=registry,ref=gitea.mrixs.me/mrixs/trip-planner:buildcache
cache-to: type=registry,ref=gitea.mrixs.me/mrixs/trip-planner:buildcache,mode=max

2
.gitignore vendored
View File

@@ -2,5 +2,5 @@
dump.rdb
coverage.out
cover.out
api
/api
*.log

View File

@@ -5,6 +5,7 @@ import (
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
@@ -44,8 +45,15 @@ func flushRedisForTest(t *testing.T, client *redis.Client) {
}
func newMockHandlerContext() *HandlerContext {
// Read Redis address from environment, default to localhost:6379
redisAddr := os.Getenv("REDIS_ADDR")
if redisAddr == "" {
redisAddr = "localhost:6379"
}
redisClient := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Addr: redisAddr,
Password: "",
DB: 0,
})
// Create an empty routing graph

View File

@@ -3,6 +3,7 @@ package main
import (
"context"
"fmt"
"os"
"testing"
"time"
@@ -14,7 +15,16 @@ import (
)
func newMockMonitor(id string, tripCount int, scheduleFunc func(context.Context, string) (int, error)) *StationMonitor {
rc := redis.NewClient(&redis.Options{Addr: "localhost:6379"})
// Read Redis address from environment, default to localhost:6379
redisAddr := os.Getenv("REDIS_ADDR")
if redisAddr == "" {
redisAddr = "localhost:6379"
}
rc := redis.NewClient(&redis.Options{
Addr: redisAddr,
Password: "",
DB: 0,
})
yc := yandex.NewClient("test-key")
monitor := &StationMonitor{
@@ -40,7 +50,16 @@ const testMonitorID = "test-station"
// gets status "active" and zero-trip day count resets to 0.
func TestProcessStation_WithTrips(t *testing.T) {
t.Helper()
rc := redis.NewClient(&redis.Options{Addr: "localhost:6379"})
// Read Redis address from environment, default to localhost:6379
redisAddr := os.Getenv("REDIS_ADDR")
if redisAddr == "" {
redisAddr = "localhost:6379"
}
rc := redis.NewClient(&redis.Options{
Addr: redisAddr,
Password: "",
DB: 0,
})
defer rc.Close()
ctx := context.Background()
@@ -84,7 +103,16 @@ func TestProcessStation_WithTrips(t *testing.T) {
// with 0 trips increments the zero-trip day count.
func TestProcessStation_ZeroTrips_IncrementsCount(t *testing.T) {
t.Helper()
rc := redis.NewClient(&redis.Options{Addr: "localhost:6379"})
// Read Redis address from environment, default to localhost:6379
redisAddr := os.Getenv("REDIS_ADDR")
if redisAddr == "" {
redisAddr = "localhost:6379"
}
rc := redis.NewClient(&redis.Options{
Addr: redisAddr,
Password: "",
DB: 0,
})
defer rc.Close()
ctx := context.Background()
@@ -128,7 +156,16 @@ func TestProcessStation_ZeroTrips_IncrementsCount(t *testing.T) {
// with 3 consecutive days of zero trips gets status "closed".
func TestProcessStation_ZeroTrips_3Days_Closes(t *testing.T) {
t.Helper()
rc := redis.NewClient(&redis.Options{Addr: "localhost:6379"})
// Read Redis address from environment, default to localhost:6379
redisAddr := os.Getenv("REDIS_ADDR")
if redisAddr == "" {
redisAddr = "localhost:6379"
}
rc := redis.NewClient(&redis.Options{
Addr: redisAddr,
Password: "",
DB: 0,
})
defer rc.Close()
ctx := context.Background()
@@ -190,7 +227,16 @@ func TestProcessStation_ZeroTrips_3Days_Closes(t *testing.T) {
// closed due to 3 zero-trip days gets reactivated when trips resume.
func TestProcessStation_Reactivation_AfterClosure(t *testing.T) {
t.Helper()
rc := redis.NewClient(&redis.Options{Addr: "localhost:6379"})
// Read Redis address from environment, default to localhost:6379
redisAddr := os.Getenv("REDIS_ADDR")
if redisAddr == "" {
redisAddr = "localhost:6379"
}
rc := redis.NewClient(&redis.Options{
Addr: redisAddr,
Password: "",
DB: 0,
})
defer rc.Close()
ctx := context.Background()
@@ -240,7 +286,16 @@ func TestProcessStation_Reactivation_AfterClosure(t *testing.T) {
// and that it reactivates when trips resume.
func TestAutoClosureChronology(t *testing.T) {
t.Helper()
rc := redis.NewClient(&redis.Options{Addr: "localhost:6379"})
// Read Redis address from environment, default to localhost:6379
redisAddr := os.Getenv("REDIS_ADDR")
if redisAddr == "" {
redisAddr = "localhost:6379"
}
rc := redis.NewClient(&redis.Options{
Addr: redisAddr,
Password: "",
DB: 0,
})
defer rc.Close()
ctx := context.Background()

4
go.mod
View File

@@ -2,9 +2,9 @@ module trip-planner
go 1.26.4
require github.com/go-redis/redis/v8 v8.11.5
require (
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/go-redis/redis/v8 v8.11.5 // indirect
github.com/jmoiron/sqlx v1.4.0 // indirect
)

24
go.sum
View File

@@ -1,12 +1,24 @@
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

View File

@@ -12,9 +12,11 @@ import (
// TestCacheGetSet tests basic Get and Set operations.
func TestCacheGetSet(t *testing.T) {
ctx := context.Background()
client := NewRedisClient(redis.NewClient(&redis.Options{
Addr: "localhost:6379",
}), metrics.New())
rdb := redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",
})
client := NewRedisClient(rdb, metrics.New())
defer rdb.Close()
// Test Set
key := &CacheKey{Kind: "city", Code: "c146"}
@@ -88,9 +90,11 @@ func TestCacheKeyString(t *testing.T) {
// TestCacheAsideGetOrSet tests the cache-aside GetOrSetFuncPattern.
func TestCacheAsideGetOrSet(t *testing.T) {
ctx := context.Background()
client := NewRedisClient(redis.NewClient(&redis.Options{
Addr: "localhost:6379",
}), metrics.New())
rdb := redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",
})
client := NewRedisClient(rdb, metrics.New())
defer rdb.Close()
fetchCallCount := 0
fetchFunc := func() ([]byte, error) {
@@ -128,9 +132,11 @@ func TestCacheAsideGetOrSet(t *testing.T) {
// TestCacheAsideGetCity tests GetCity with cache.
func TestCacheAsideGetCity(t *testing.T) {
ctx := context.Background()
client := NewRedisClient(redis.NewClient(&redis.Options{
Addr: "localhost:6379",
}), metrics.New())
rdb := redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",
})
client := NewRedisClient(rdb, metrics.New())
defer rdb.Close()
fetchCallCount := 0
fetchFunc := func() ([]byte, error) {
@@ -167,9 +173,11 @@ func TestCacheAsideGetCity(t *testing.T) {
// TestCacheAsideGetSearch tests GetSearch with near-term and far-term TTL.
func TestCacheAsideGetSearch(t *testing.T) {
ctx := context.Background()
client := NewRedisClient(redis.NewClient(&redis.Options{
Addr: "localhost:6379",
}), metrics.New())
rdb := redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",
})
client := NewRedisClient(rdb, metrics.New())
defer rdb.Close()
fetchNearTerm := func() ([]byte, error) {
return []byte(`{"near_term":true}`), nil
@@ -205,9 +213,11 @@ func TestCacheAsideGetSearch(t *testing.T) {
// TestCacheInvalidate tests invalidation operations.
func TestCacheInvalidate(t *testing.T) {
ctx := context.Background()
client := NewRedisClient(redis.NewClient(&redis.Options{
Addr: "localhost:6379",
}), metrics.New())
rdb := redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",
})
client := NewRedisClient(rdb, metrics.New())
defer rdb.Close()
// Set up some keys
cityKey := &CacheKey{Kind: "city", Code: "c146"}