hako/e2e/e2eutil/container.go
Felipe M. 54760805c2
All checks were successful
CI / goreleaser-lint (push) Successful in 7s
CI / format (push) Successful in 54s
CI / lint (push) Successful in 6m37s
CI / build (push) Successful in 6m55s
CI / test (push) Successful in 8m7s
CI / e2e (push) Successful in 24m20s
deps: upgrade server, webapp and e2e dependencies (#2)
Upgrades every dependency in the server, the webapp and the e2e module, and fixes the CI breakage found along the way.

## Server

All direct and indirect dependencies to latest: goquery 1.9.2 → 1.13.0, go-sqlbuilder 1.38.2 → 1.43.0, cobra 1.9.1 → 1.10.2, testify 1.11.1 → 1.12.1, x/crypto 0.46.0 → 0.57.0, modernc.org/sqlite 1.34.1 → 1.59.0.

The `go` directive moves to **1.26.0** because the `golang.org/x` modules now require it. The Actions workflows read `go-version-file`, so they follow automatically.

## Webapp

Major bumps: pinia 2 → 4, vue-router 4 → 5, vite 6 → 8, eslint 9 → 10, vue-tsc 2 → 3, `@vitejs/plugin-vue` 5 → 6, pdfjs-dist 5 → 6, marked 17 → 18.

**TypeScript stays on 5.9.3.** `typescript-eslint` declares `typescript >=4.8.4 <6.1.0` and hard-errors under TS 7 (`typescript-eslint does not support TS 7.0`); TS 6 exists only as `6.0.0-beta`. 5.9.3 is the highest version that keeps `make lint-webapp` working. Tracking: typescript-eslint#10940.

Dropped two dead dependencies: `@codemirror/basic-setup` (no importers) and `@types/dompurify` (npm-deprecated stub; dompurify ships its own types).

Vite 8 builds with rolldown, which rejects a type imported as a value, so `Extension` becomes an `import type` in `JsonEditor.vue`.

## Fixes found on the way

**`tsconfig.app.json` inherited nothing.** It extended `./tsconfig.json`, a solution file holding only `files: []` and `references`, so it picked up no `target`, `lib`, `jsx` or `noEmit`. Type checks failed on every built-in type (`Cannot find name 'Map'`) and `vue-tsc -b` emitted JavaScript next to the TypeScript sources, which then shadowed them and broke `vite build`. Now extends `@vue/tsconfig/tsconfig.dom.json` — already a devDependency, until now unused — and sets `noEmit`.

With type checking working again, eight pre-existing app-code type errors are now visible (PDF render params, an unused `@ts-expect-error`, `HeadersInit` indexing, a missing `role`, `total_size`, a route `q` param). **Left untouched** — they pre-date this branch, verified by running `vue-tsc` against the old dependency set and diffing: byte-identical output.

**`make ci-lint` could not work.** It pinned golangci-lint `v1.64.5` and installed from the pre-v2 module path, against a `version: "2"` config. Now `v2.13.2` on `.../golangci-lint/v2/cmd/...`.

**e2e on testcontainers 0.44** rather than 0.40: `MappedPort` now takes a `string` instead of a `nat.Port`.

## Verification

`go build` · `go vet` (root + e2e) · `go test -race` 11/11 packages · `go mod tidy` no-op on both modules · `golangci-lint` v2.13.2 0 issues · `goreleaser check` · `vite build` · `eslint` 0 problems · `vue-tsc` identical to the pre-upgrade baseline.

The e2e suite went 41 pass / 6 fail → **47 pass / 0 fail**.

The e2e suite was re-run on the merged tree and is green there too: **47 pass / 0 fail** (`ok github.com/fmartingr/hako/e2e/playwright 996.259s`). That confirms the one combination neither branch had exercised — master's shared-network e2e code running against testcontainers 0.44 rather than the 0.40 it was written for.

## Merge note

Master's Forgejo Actions migration independently fixed the same three e2e bugs this branch did (the `.link-item` selector, the `CreateLink` navigation guard, `IsVisible` strict mode). Master's wording was kept for all three, along with its shared-Docker-network approach, which is better than the host-gateway route used here — it does not depend on Docker Desktop and works under the Docker-in-Docker service the new e2e job uses.

Consequently `eca5c88` is largely inert: its branch-gating and Go-version changes edited `.woodpecker/` files the merge deleted. Only its golangci-lint fix survives into the tree.

Reviewed-on: #2
2026-09-22 20:10:10 +02:00

108 lines
3 KiB
Go

package e2eutil
import (
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"testing"
"time"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
)
// StartHakoContainer starts a Hako container for E2E testing.
// It builds the webapp, compiles the binary, creates a container, and returns
// the container instance and base URL for accessing the server.
func StartHakoContainer(ctx context.Context, t *testing.T) (testcontainers.Container, string, error) {
t.Helper()
// Get the project root (one level up from e2e)
projectRoot, err := filepath.Abs(filepath.Join("..", ".."))
if err != nil {
return nil, "", fmt.Errorf("failed to get project root: %w", err)
}
// Build webapp
t.Log("Building webapp...")
cmd := exec.CommandContext(ctx, "make", "build-webapp")
cmd.Dir = projectRoot
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return nil, "", fmt.Errorf("failed to build webapp: %w", err)
}
// Build Hako binary for the container (Linux)
t.Log("Building Hako binary...")
binaryPath := filepath.Join(projectRoot, "e2e", "hako")
cmd = exec.CommandContext(ctx, "go", "build", "-o", binaryPath, "./cmd/hako")
cmd.Dir = projectRoot
cmd.Env = append(os.Environ(), "CGO_ENABLED=0", "GOOS=linux", "GOARCH=amd64")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return nil, "", fmt.Errorf("failed to build binary: %w", err)
}
// Ensure binary is removed on cleanup
t.Cleanup(func() {
os.Remove(binaryPath)
})
// Ensure shared network exists so Hako can reach the test web server by alias.
nw, err := getSharedNetwork(ctx)
if err != nil {
return nil, "", err
}
// Create container request attached to the shared network.
req := testcontainers.ContainerRequest{
FromDockerfile: testcontainers.FromDockerfile{
Context: filepath.Join(projectRoot, "e2e"),
Dockerfile: "Dockerfile.e2e",
PrintBuildLog: true,
},
ExposedPorts: []string{"8080/tcp"},
WaitingFor: wait.ForLog("Starting HTTP server").
WithStartupTimeout(30 * time.Second),
Networks: []string{nw.Name},
}
// Start container
t.Log("Starting Hako container...")
container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: req,
Started: true,
})
if err != nil {
return nil, "", fmt.Errorf("failed to start container: %w", err)
}
// Register cleanup
t.Cleanup(func() {
if err := container.Terminate(ctx); err != nil {
t.Logf("Failed to terminate container: %v", err)
}
})
// Get the mapped port
mappedPort, err := container.MappedPort(ctx, "8080/tcp")
if err != nil {
return nil, "", fmt.Errorf("failed to get mapped port: %w", err)
}
// Get the host
host, err := container.Host(ctx)
if err != nil {
return nil, "", fmt.Errorf("failed to get container host: %w", err)
}
// Construct base URL
baseURL := fmt.Sprintf("http://%s:%s", host, mappedPort.Port())
t.Logf("Hako container started at %s", baseURL)
return container, baseURL, nil
}