3.7 KiB
3.7 KiB
General
- Before scaffolding any service, verify current stable versions from official sources (go.dev, rust-lang.org, nodejs.org, etc.)
- Prefer stdlib over dependencies where reasonable
- No vendoring unless explicitly requested
Go
- Use latest stable Go version (check https://go.dev/dl/ before starting)
- Use
slogfor structured logging context.Contextfirst param, always- No
init()unless unavoidable - Errors: prefer
errors.Joinfor multiple errors,fmt.Errorfwith%wfor wrapping
Containers
- Multi-stage builds, distroless/scratch final images
- ubuntu based images for the development builds
- Non-root user, read-only rootfs for production builds
- prepare dockerfiles for both development and production
- Pin base image digests in prod Dockerfiles
K8s Manifests
- Always include resource requests/limits
- Liveness/readiness probes with sane defaults
- PodDisruptionBudgets for replicated workloads
Code Style
- Fail fast, return early
- Table-driven tests
- No obvious comments
CI/CD
- Git remote: Gitea at https://gitea.yourdomain.com
- CI: Gitea Actions (workflow syntax is GitHub Actions compatible)
- Workflows go in
.gitea/workflows/ - Use
runs-on: linux-amd64(or whatever labels your runners have) - Registry: gitea.home.hrajfrisbee.cz (use
${{ secrets.REGISTRY_TOKEN }}for auth)
Testing (Go)
Go Test Requirements
- MANDATORY: All new Go code must have corresponding tests
- Use stdlib
testingpackage, no testify unless already in project - Table-driven tests as default pattern
- Test file next to source:
foo.go→foo_test.go - Name tests
Test<Function>_<scenario>(e.g.,TestCreateUser_duplicateEmail) - Use
t.Parallel()where safe - Subtests for table entries:
t.Run(tc.name, func(t *testing.T) { ... }) - Test behavior, not implementation
- Use
testing.Short()to skip slow integration tests:if testing.Short() { t.Skip() }
Test Structure
- Each test function should test ONE behavior
- Use descriptive test names:
TestExerciseHandler_Create_ValidInput - Table-driven test format:
tests := []struct { name string input InputType want ExpectedType wantErr bool }{ {"valid input", validInput, expectedOutput, false}, {"empty name", emptyName, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // test logic }) }
Handler Tests (Unit)
- Mock external dependencies (repositories, services)
- Use
httptest.NewRecorder()andhttptest.NewRequest() - Test: success path, validation errors, not found, internal errors
- Mock interfaces defined in
internal/api/interfaces.go - Mock implementations in
internal/api/mocks_test.go
Repository Tests (Integration)
- Use testcontainers-go for database tests
- Integration tests in
_test.gowith build tag//go:build integration - Clean database state between tests using
truncateTables() - Test: CRUD operations, edge cases, transactions, cascades
- Test helpers in
internal/storage/testhelpers_test.go
What to Test
- All public functions/methods
- Happy path AND error paths
- Edge cases: empty inputs, nil values, not found
- Validation logic
- Transaction rollback scenarios
What NOT to Test
- Simple getters/setters
- Third-party library code
- Private helper functions (test through public API)
Running Tests
# Run all unit tests (fast)
go test ./...
# Run with verbose output
go test -v ./...
# Run with coverage
go test -cover ./...
# Run only unit tests, skip integration
go test -short ./...
# Run integration tests (requires Docker)
go test -v -tags=integration ./internal/storage/...
# Run specific package
go test -v ./internal/api/...