Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
6824328
Add unit tests and CI workflow
gocanto Jul 31, 2025
4c949ef
style: adjust tests and workflow
gocanto Jul 31, 2025
879e9c8
Fix tests and rename validation method
gocanto Jul 31, 2025
6bc8660
Fix tests workflow and encryption test
gocanto Jul 31, 2025
9b30f93
Update tests workflow conditions and coverage
gocanto Jul 31, 2025
01fd154
Increase test coverage
gocanto Jul 31, 2025
d227917
Add tests for boost and env packages
gocanto Jul 31, 2025
5a33e8b
Run all tests in workflow
gocanto Jul 31, 2025
f310bce
Update tests workflow and make secrets path configurable
gocanto Jul 31, 2025
b4d0e3a
Include boost and env packages in tests
gocanto Jul 31, 2025
7e02641
chore(ci): cache deps and test pkg coverage
gocanto Jul 31, 2025
0019e0e
Run boost and env tests with coverage
gocanto Jul 31, 2025
9f6e926
Run coverage in each test step
gocanto Jul 31, 2025
9725703
Update PR label for tests
gocanto Jul 31, 2025
9f078b9
Increase boost test coverage
gocanto Jul 31, 2025
16a3b36
Add handler tests and workflow step
gocanto Jul 31, 2025
85e81b5
test: cover all handlers
gocanto Jul 31, 2025
f839d93
Use testcontainers for DB tests and update workflow
gocanto Aug 1, 2025
b342a30
Add pkg test workflow and update label
gocanto Aug 1, 2025
b57a67e
Remove pkg-tests workflow and adjust test trigger
gocanto Aug 1, 2025
3a558ea
Fix database tests and cache modules
gocanto Aug 1, 2025
0329b5e
Fix workflow label
gocanto Aug 1, 2025
a7f7fef
Remove redundant cache step
gocanto Aug 1, 2025
e34c0f4
Add seeder tests with sqlite and update CI
gocanto Aug 1, 2025
21ea1de
Use Postgres container for seeder tests
gocanto Aug 1, 2025
eb0145e
Fix seeder datetime type and add pkg tests workflow
gocanto Aug 1, 2025
3a608eb
Format new tests
gocanto Aug 1, 2025
0e12cda
Remove pkg tests workflow
gocanto Aug 1, 2025
59baa80
Add pkg tests workflow
gocanto Aug 1, 2025
51fd169
format
gocanto Aug 1, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Tests

on:
pull_request:
types: [ready_for_review, synchronize, labeled]

jobs:
test:
if: github.event.pull_request.draft == false || (github.event.action == 'labeled' && github.event.label.name == 'testing')
runs-on: ubuntu-latest
strategy:
matrix:
go-version: [1.24.x]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify Go version 1.24.x availability.

Go 1.24.x may not be available yet. Consider using a stable version like 1.23.x or 1.22.x.


🌐 Web query:

What is the latest stable version of Go as of 2024?

💡 Result:

As of August 1, 2025, the latest stable version of Go is 1.24.5, released on July 8, 2025. This version includes security fixes to the go command, as well as bug fixes to the compiler, the linker, and the runtime. (go.dev)

Go 1.24, released on February 11, 2025, introduced several notable features:

  • Generic Type Aliases: Go 1.24 fully supports generic type aliases, allowing type aliases to be parameterized like defined types. (tip.golang.org)

  • Performance Improvements: The runtime has been optimized, resulting in a 2–3% average decrease in CPU overhead across various benchmarks. (go.dev)

  • Tool Enhancements: The go command now offers a mechanism for tracking tool dependencies within a module, and the go vet subcommand includes a new analyzer for reporting common mistakes in test declarations. (go.dev)

For a comprehensive list of changes and improvements, you can refer to the Go 1.24 release notes. (tip.golang.org)

Citations:


Go 1.24.x is GA—no downgrade needed; improve coverage handling

  • The workflow’s go-version: [1.24.x] is valid (it will resolve to Go 1.24.5 as of July 2025). For stricter reproducibility, pin to the patch release:
    .github/workflows/tests.yml
    — Line 13
    -      go-version: [1.24.x]
    +      go-version: ['1.24.5']
  • Prevent coverage files from clobbering each other and produce a single report:
    • Emit distinct coverage files per test step (e.g. coverage-app.out, coverage-api.out).
    • After all tests, add:
    - name: Merge coverage reports
      run: go tool cover -merge=coverage-*.out -o coverage.out
    - name: Upload coverage artifact
      uses: actions/upload-artifact@v3
      with:
        name: coverage-report
        path: coverage.out
🤖 Prompt for AI Agents
In .github/workflows/tests.yml at line 13, update the Go version from a floating
patch version "1.24.x" to the exact patch release "1.24.5" for stricter
reproducibility. Modify the coverage handling by generating distinct coverage
files for each test step (e.g., coverage-app.out, coverage-api.out) to avoid
overwriting. Then, add steps after all tests to merge these coverage files into
a single coverage.out file using "go tool cover -merge" and upload this merged
coverage report as an artifact using the actions/upload-artifact@v3 action.

steps:
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}

- uses: actions/checkout@v4

- uses: actions/cache@v4
with:
path: |
~/go/pkg/mod
~/.cache/go-build
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-

- name: Run pkg tests
run: |
go test ./pkg/... -coverprofile=coverage.out
go tool cover -func=coverage.out | tail -n 1

- name: Run boost & env tests
run: |
go test ./boost ./env -coverprofile=coverage.out
go tool cover -func=coverage.out | tail -n 1

- name: Run handlers tests
run: |
go test ./handler/... -coverprofile=coverage.out
go tool cover -func=coverage.out | tail -n 1

- name: Run database tests
run: |
go test ./database/... -coverprofile=coverage.out
go tool cover -func=coverage.out | tail -n 1
262 changes: 262 additions & 0 deletions boost/boost_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
package boost

import (
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"

"github.com/oullin/database"
"github.com/oullin/database/repository"
"github.com/oullin/pkg"
"github.com/oullin/pkg/auth"
"github.com/oullin/pkg/http/middleware"
"github.com/oullin/pkg/llogs"
)

func validEnvVars(t *testing.T) {
t.Setenv("ENV_APP_NAME", "guss")
t.Setenv("ENV_APP_ENV_TYPE", "local")
t.Setenv("ENV_APP_MASTER_KEY", "12345678901234567890123456789012")
t.Setenv("ENV_DB_USER_NAME", "usernamefoo")
t.Setenv("ENV_DB_USER_PASSWORD", "passwordfoo")
t.Setenv("ENV_DB_DATABASE_NAME", "dbnamefoo")
t.Setenv("ENV_DB_PORT", "5432")
t.Setenv("ENV_DB_HOST", "localhost")
t.Setenv("ENV_DB_SSL_MODE", "require")
t.Setenv("ENV_DB_TIMEZONE", "UTC")
t.Setenv("ENV_APP_LOG_LEVEL", "debug")
t.Setenv("ENV_APP_LOGS_DIR", "logs_%s.log")
t.Setenv("ENV_APP_LOGS_DATE_FORMAT", "2006_01_02")
t.Setenv("ENV_HTTP_HOST", "localhost")
t.Setenv("ENV_HTTP_PORT", "8080")
t.Setenv("ENV_SENTRY_DSN", "dsn")
t.Setenv("ENV_SENTRY_CSP", "csp")
}

func TestMakeEnv(t *testing.T) {
validEnvVars(t)

env := MakeEnv(pkg.GetDefaultValidator())

if env.App.Name != "guss" {
t.Fatalf("env not loaded")
}
}

func TestIgnite(t *testing.T) {
content := "ENV_APP_NAME=guss\n" +
"ENV_APP_ENV_TYPE=local\n" +
"ENV_APP_MASTER_KEY=12345678901234567890123456789012\n" +
"ENV_DB_USER_NAME=usernamefoo\n" +
"ENV_DB_USER_PASSWORD=passwordfoo\n" +
"ENV_DB_DATABASE_NAME=dbnamefoo\n" +
"ENV_DB_PORT=5432\n" +
"ENV_DB_HOST=localhost\n" +
"ENV_DB_SSL_MODE=require\n" +
"ENV_DB_TIMEZONE=UTC\n" +
"ENV_APP_LOG_LEVEL=debug\n" +
"ENV_APP_LOGS_DIR=logs_%s.log\n" +
"ENV_APP_LOGS_DATE_FORMAT=2006_01_02\n" +
"ENV_HTTP_HOST=localhost\n" +
"ENV_HTTP_PORT=8080\n" +
"ENV_SENTRY_DSN=dsn\n" +
"ENV_SENTRY_CSP=csp\n"

f, err := os.CreateTemp("", "envfile")
if err != nil {
t.Fatalf("temp file err: %v", err)
}
defer os.Remove(f.Name())
f.WriteString(content)
f.Close()

env := Ignite(f.Name(), pkg.GetDefaultValidator())

if env.Network.HttpPort != "8080" {
t.Fatalf("env not loaded")
}
}

func TestAppBootNil(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Fatalf("expected panic")
}
}()

var a *App
a.Boot()
}

func TestAppHelpers(t *testing.T) {
app := &App{}
mux := http.NewServeMux()
r := Router{Mux: mux}
app.SetRouter(r)

if app.GetMux() != mux {
t.Fatalf("mux not set")
}

app.CloseLogs()
app.CloseDB()

if app.GetEnv() != nil {
t.Fatalf("expected nil env")
}
if app.GetDB() != nil {
t.Fatalf("expected nil db")
}
}
func TestAppBootRoutes(t *testing.T) {
validEnvVars(t)

env := MakeEnv(pkg.GetDefaultValidator())

key, err := auth.GenerateAESKey()
if err != nil {
t.Fatalf("key err: %v", err)
}

handler, err := auth.MakeTokensHandler(key)
if err != nil {
t.Fatalf("handler err: %v", err)
}

router := Router{
Env: env,
Mux: http.NewServeMux(),
Pipeline: middleware.Pipeline{
Env: env,
ApiKeys: &repository.ApiKeys{DB: &database.Connection{}},
TokenHandler: handler,
},
Db: &database.Connection{},
}

app := &App{}
app.SetRouter(router)

app.Boot()

routes := []struct {
method string
path string
}{
{"GET", "/profile"},
{"GET", "/experience"},
{"GET", "/projects"},
{"GET", "/social"},
{"GET", "/talks"},
{"GET", "/education"},
{"GET", "/recommendations"},
{"POST", "/posts"},
{"GET", "/posts/slug"},
{"GET", "/categories"},
}

for _, rt := range routes {
req := httptest.NewRequest(rt.method, rt.path, nil)
h, pattern := app.GetMux().Handler(req)
if pattern == "" || h == nil {
t.Fatalf("route missing %s %s", rt.method, rt.path)
}
}
}

func TestMakeLogs(t *testing.T) {
dir, err := os.MkdirTemp("", "logdir")
if err != nil {
t.Fatalf("tmpdir err: %v", err)
}

validEnvVars(t)
t.Setenv("ENV_APP_LOGS_DIR", filepath.Join(dir, "log-%s.txt"))

env := MakeEnv(pkg.GetDefaultValidator())

d := MakeLogs(env)
driver := *d
fl := driver.(llogs.FilesLogs)

if !strings.HasPrefix(fl.DefaultPath(), dir) {
t.Fatalf("wrong log dir")
}

if !fl.Close() {
t.Fatalf("close failed")
}
}

func TestMakeDbConnectionPanic(t *testing.T) {
validEnvVars(t)
t.Setenv("ENV_DB_PORT", "1")
t.Setenv("ENV_SENTRY_DSN", "https://public@o0.ingest.sentry.io/0")

env := MakeEnv(pkg.GetDefaultValidator())

defer func() {
if r := recover(); r == nil {
t.Fatalf("expected panic")
}
}()

MakeDbConnection(env)
}

func TestMakeAppPanic(t *testing.T) {
validEnvVars(t)
t.Setenv("ENV_DB_PORT", "1")
t.Setenv("ENV_APP_LOGS_DIR", "/tmp/log-%s.txt")
t.Setenv("ENV_SENTRY_DSN", "https://public@o0.ingest.sentry.io/0")

env := MakeEnv(pkg.GetDefaultValidator())

defer func() {
if r := recover(); r == nil {
t.Fatalf("expected panic")
}
}()

MakeApp(env, pkg.GetDefaultValidator())
}

func TestMakeSentry(t *testing.T) {
validEnvVars(t)
t.Setenv("ENV_SENTRY_DSN", "https://public@o0.ingest.sentry.io/0")

env := MakeEnv(pkg.GetDefaultValidator())

s := MakeSentry(env)
if s == nil || s.Handler == nil || s.Options == nil {
t.Fatalf("sentry setup failed")
}
}

func TestCloseLogs(t *testing.T) {
dir, err := os.MkdirTemp("", "logdir")
if err != nil {
t.Fatalf("tmpdir err: %v", err)
}

validEnvVars(t)
t.Setenv("ENV_APP_LOGS_DIR", filepath.Join(dir, "log-%s.txt"))
t.Setenv("ENV_SENTRY_DSN", "https://public@o0.ingest.sentry.io/0")

env := MakeEnv(pkg.GetDefaultValidator())

l := MakeLogs(env)
app := &App{logs: l}

app.CloseLogs()
}

func TestGetMuxNil(t *testing.T) {
app := &App{}
if app.GetMux() != nil {
t.Fatalf("expected nil mux")
}
}
12 changes: 6 additions & 6 deletions boost/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,23 +93,23 @@ func MakeEnv(validate *pkg.Validator) *env.Environment {
}

if _, err := validate.Rejects(app); err != nil {
panic(errorSuffix + "invalid [APP] model: " + validate.GetErrorsAsJason())
panic(errorSuffix + "invalid [APP] model: " + validate.GetErrorsAsJson())
}

if _, err := validate.Rejects(db); err != nil {
panic(errorSuffix + "invalid [Sql] model: " + validate.GetErrorsAsJason())
panic(errorSuffix + "invalid [Sql] model: " + validate.GetErrorsAsJson())
}

if _, err := validate.Rejects(logsCreds); err != nil {
panic(errorSuffix + "invalid [logs Creds] model: " + validate.GetErrorsAsJason())
panic(errorSuffix + "invalid [logs Creds] model: " + validate.GetErrorsAsJson())
}

if _, err := validate.Rejects(net); err != nil {
panic(errorSuffix + "invalid [NETWORK] model: " + validate.GetErrorsAsJason())
panic(errorSuffix + "invalid [NETWORK] model: " + validate.GetErrorsAsJson())
}

if _, err := validate.Rejects(sentryEnvironment); err != nil {
panic(errorSuffix + "invalid [SENTRY] model: " + validate.GetErrorsAsJason())
panic(errorSuffix + "invalid [SENTRY] model: " + validate.GetErrorsAsJson())
}

blog := &env.Environment{
Expand All @@ -121,7 +121,7 @@ func MakeEnv(validate *pkg.Validator) *env.Environment {
}

if _, err := validate.Rejects(blog); err != nil {
panic(errorSuffix + "invalid blog [ENVIRONMENT] model: " + validate.GetErrorsAsJason())
panic(errorSuffix + "invalid blog [ENVIRONMENT] model: " + validate.GetErrorsAsJson())
}

return blog
Expand Down
2 changes: 1 addition & 1 deletion cli/panel/menu.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ func (p *Menu) CapturePostURL() (*posts.Input, error) {
cli.Reset,
cli.BlueColour,
cli.Reset,
validate.GetErrorsAsJason(),
validate.GetErrorsAsJson(),
)
}

Expand Down
Loading
Loading