ci: migrate from Woodpecker CI to Forgejo Actions #1

Open
fmartingr wants to merge 14 commits from feat/forgejo-actions into master
22 changed files with 293 additions and 279 deletions

7
.dockerignore Normal file
View file

@ -0,0 +1,7 @@
hako
hako.db
dist/
build/
devdata/
.git/
webapp/node_modules/

87
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,87 @@
name: CI
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
format:
runs-on: docker
container: git.nakama.town/fmartingr/ci-images/ci-base:1.1.0
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version-file: go.mod
- run: make format
- run: git diff --exit-code
goreleaser-lint:
runs-on: docker
container: git.nakama.town/fmartingr/ci-images/ci-base:1.1.0
steps:
- uses: actions/checkout@v7
- uses: actions/goreleaser-action@v6.4.0
with:
args: check
lint:
runs-on: docker
container: git.nakama.town/fmartingr/ci-images/ci-base:1.1.0
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version-file: go.mod
- run: make ci-lint
test:
runs-on: docker
container: git.nakama.town/fmartingr/ci-images/ci-base:1.1.0
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version-file: go.mod
- run: make test
e2e:
runs-on: docker
container: git.nakama.town/fmartingr/ci-images/ci-base:1.1.0
services:
dind:
image: docker:27-dind
env:
DOCKER_TLS_CERTDIR: ""
options: --privileged
env:
DOCKER_HOST: tcp://dind:2375
TESTCONTAINERS_RYUK_DISABLED: true
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version-file: e2e/go.mod
- name: Wait for Docker
run: |
for i in $(seq 1 60); do
docker info >/dev/null 2>&1 && exit 0
sleep 1
done
echo "Docker daemon not ready" >&2
exit 1
- name: Install Playwright browsers
run: go run github.com/playwright-community/playwright-go/cmd/playwright@v0.5200.1 install chromium
- run: make e2e
build:
runs-on: docker
container: git.nakama.town/fmartingr/ci-images/ci-base:1.1.0
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version-file: go.mod
- run: make build

33
.github/workflows/release.yml vendored Normal file
View file

@ -0,0 +1,33 @@
name: Release
on:
push:
tags: ["v*"]
jobs:
release:
runs-on: docker
container: git.nakama.town/fmartingr/ci-images/ci-base:1.1.0
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Install Go
uses: actions/setup-go@v7
with:
go-version-file: go.mod
- name: Setup Docker buildx
run: docker buildx create --use --name multiplatform --driver docker-container
- name: Login to container registry
run: echo "${{ secrets.FORGEJO_TOKEN }}" | docker login git.nakama.town -u ${{ github.actor }} --password-stdin
- name: Run GoReleaser
uses: actions/goreleaser-action@v6.4.0
with:
args: release --clean
env:
GORELEASER_FORCE_TOKEN: gitea
GITEA_TOKEN: ${{ secrets.FORGEJO_TOKEN }}

View file

@ -57,6 +57,7 @@ dockers_v2:
- "git.nakama.town/fmartingr/hako"
tags:
- "{{ .Version }}"
- latest
dockerfile: Containerfile
platforms:
- linux/amd64
@ -65,7 +66,7 @@ dockers_v2:
nfpms:
- maintainer: Felipe Martin <me@fmartingr.com>
description: SMTP server to forward messages to shoutrrr endpoints
description: Self-hosted link archival service
homepage: https://git.nakama.town/fmartingr/hako
license: AGPL-3.0
formats:
@ -119,4 +120,7 @@ changelog:
- "^chore\\(deps\\):"
release:
gitea:
owner: fmartingr
name: hako
prerelease: auto

View file

@ -1,51 +0,0 @@
when:
event:
- push
- pull_request
branch:
- main
steps:
format:
image: golang:1.24
commands:
- make format
- git diff --exit-code # Fail if files were changed
lint-goreleaser:
image: goreleaser/goreleaser:latest
commands:
- goreleaser check
lint:
image: golang:1.24
commands:
- make ci-lint
test:
image: golang:1.24
commands:
- make test
e2e:
image: golang:1.24
environment:
TESTCONTAINERS_RYUK_DISABLED: true
DOCKER_HOST: unix:///var/run/docker.sock
volumes:
- /var/run/docker.sock:/var/run/docker.sock
commands:
# Install system dependencies
- apt-get update && apt-get install -y docker.io curl unzip
# Install Bun for webapp build
- curl -fsSL https://bun.sh/install | bash
- export PATH="$HOME/.bun/bin:$PATH"
# Install Playwright browsers
- go run github.com/playwright-community/playwright-go/cmd/playwright@latest install --with-deps chromium
# Run e2e tests
- make e2e
when:
status: [success, failure]

View file

@ -1,47 +0,0 @@
when:
- event: tag
branch: main
steps:
- name: Release
image: ubuntu:24.04
environment:
GITEA_TOKEN:
from_secret: GITEA_TOKEN
DOCKER_HOST: unix:///var/run/docker.sock
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
commands:
# Install system dependencies
- apt-get update
- apt-get install -y ca-certificates curl git make wget unzip upx-ucl
# Install Docker from official repository (for buildx support)
- curl -fsSL https://get.docker.com | sh
# Install Go 1.23
- wget -q https://go.dev/dl/go1.23.5.linux-amd64.tar.gz
- tar -C /usr/local -xzf go1.23.5.linux-amd64.tar.gz
- rm go1.23.5.linux-amd64.tar.gz
- export PATH="/usr/local/go/bin:$HOME/go/bin:$PATH"
# Install GoReleaser via go install
- go install github.com/goreleaser/goreleaser/v2@latest
# Install Bun (using official installer)
- curl -fsSL https://bun.sh/install | bash
- export PATH="$HOME/.bun/bin:$PATH"
# Setup Docker buildx for multi-platform builds
- docker buildx create --use --name multiplatform --driver docker-container
# Verify installations
- docker --version
- docker buildx version
- goreleaser --version
- bun --version
- upx --version
# Authenticate and release
- docker login -u fmartingr -p $GITEA_TOKEN git.nakama.town
- goreleaser release --clean

View file

@ -1,36 +1,20 @@
# This file is used directly by the goreleaser build
# It is used to build the final container image
# To build the image locally use make build-docker
# Runtime image
FROM alpine:3.20
FROM alpine:3.23
LABEL maintainer="Felipe Martin <me@fmartingr.com>"
LABEL org.opencontainers.image.source="https://git.nakama.town/fmartingr/hako"
# Install runtime dependencies (yt-dlp and ffmpeg for video archival from Alpine community)
RUN apk add --no-cache \
poppler-utils \
ca-certificates \
tzdata \
&& apk add --no-cache --repository https://dl-cdn.alpinelinux.org/alpine/v3.20/community yt-dlp ffmpeg
&& apk add --no-cache --repository https://dl-cdn.alpinelinux.org/alpine/v3.23/community yt-dlp ffmpeg
# Create non-root user
RUN addgroup -g 1000 hako && \
adduser -D -u 1000 -G hako hako
RUN addgroup -g 1000 hako && adduser -u 1000 -G hako -s /bin/sh -D hako
WORKDIR /app
COPY hako /usr/local/bin/hako
RUN chown -R hako:hako /app
# Copy binary from goreleaser build context
# With dockers_v2, goreleaser provides the pre-built binary at $TARGETPLATFORM/hako
# The binary already has the webapp embedded via //go:embed
ARG TARGETPLATFORM
COPY ${TARGETPLATFORM}/hako /usr/local/bin/hako
RUN chmod +x /usr/local/bin/hako && \
chown -R hako:hako /app
# Switch to non-root user
USER hako
EXPOSE 8080
ENTRYPOINT ["/usr/local/bin/hako"]

162
Makefile
View file

@ -1,156 +1,104 @@
PROJECT_NAME := hako
SOURCE_FILES ?=./...
TEST_OPTIONS ?= -v -failfast -race -bench=. -benchtime=100000x -cover -coverprofile=coverage.out
TEST_TIMEOUT ?=1m
GOLANGCI_LINT_VERSION ?= v1.64.5
CLEAN_OPTIONS ?=-modcache -testcache
CGO_ENABLED := 0
BUILDS_PATH := ./dist
FROM_MAKEFILE := y
DIST_PATH := ./dist
TEST_OPTIONS := -v -failfast -race -bench=. -benchtime=100000x -cover -coverprofile=coverage.out
TEST_TIMEOUT := 5m
CONTAINERFILE_NAME := Containerfile
CONTAINER_ALPINE_VERSION := 3.20
CONTAINER_SOURCE_URL := "https://git.nakama.town/fmartingr/${PROJECT_NAME}"
CONTAINER_MAINTAINER := "Felipe Martin <me@fmartingr.com>"
CONTAINER_BIN_NAME := ${PROJECT_NAME}
GOLANGCI_LINT_VERSION := v1.64.5
BUILDX_PLATFORMS := linux/amd64,linux/arm64,linux/arm/v7
.DEFAULT_GOAL := help
export PROJECT_NAME
export FROM_MAKEFILE
export CGO_ENABLED
export SOURCE_FILES
export TEST_OPTIONS
export TEST_TIMEOUT
export BUILDS_PATH
export CONTAINERFILE_NAME
export CONTAINER_ALPINE_VERSION
export CONTAINER_SOURCE_URL
export CONTAINER_MAINTAINER
export CONTAINER_BIN_NAME
export BUILDX_PLATFORMS
.PHONY: all
all: help
# this is godly
# https://news.ycombinator.com/item?id=11939200
## help: Display this help message
.PHONY: help
help: ### this screen. Keep it first target to be default
ifeq ($(UNAME), Linux)
@grep -P '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | \
awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
else
@# this is not tested, but prepared in advance for you, Mac drivers
@awk -F ':.*###' '$$0 ~ FS {printf "%15s%s\n", $$1 ":", $$2}' \
$(MAKEFILE_LIST) | grep -v '@awk' | sort
endif
help:
@echo "Available targets:"
@grep -E '^## ' $(MAKEFILE_LIST) | sed 's/## / /' | sort
## clean: Clean test cache, build files
.PHONY: clean
clean: ### clean test cache, build files
$(info: Make: Clean)
@rm -rf ${BUILDS_PATH}
clean:
@rm -rf $(DIST_PATH)
@rm -rf webapp/dist
@rm -rf webapp/node_modules
@rm -f webapp/bun.lockb
@go clean ${CLEAN_OPTIONS}
@find devdata -mindepth 1 ! -name config.yaml -delete 2>/dev/null || true
@-docker buildx rm ${PROJECT_NAME}_builder
@go clean -modcache -testcache
## clean-devdata: Clear devdata database and files (keeps config.yaml)
.PHONY: clean-devdata
clean-devdata: ### Clear devdata database and files (keeps config.yaml)
$(info: Make: Cleaning devdata)
clean-devdata:
@rm -rf devdata/data
@rm -f devdata/hako.db devdata/hako.db-shm devdata/hako.db-wal
## build: Build project using goreleaser (snapshot)
.PHONY: build
build: build-webapp ### builds the project for the setup os/arch combinations
$(info: Make: Build)
@goreleaser --clean --snapshot
build: build-webapp
goreleaser build --snapshot --clean
## build-webapp: Build the webapp
.PHONY: build-webapp
build-webapp: ### Build the webapp
$(info: Make: Building webapp)
build-webapp:
@which bun > /dev/null || (echo "Error: Bun is not installed. Install it from https://bun.sh" && exit 1)
@cd webapp && bun install && bun --bun node_modules/.bin/vite build
## build-docker: Build Docker image locally via goreleaser
.PHONY: build-docker
build-docker: build-webapp
goreleaser release --snapshot --clean
## run: Build frontend and run the server
.PHONY: run
run: build-webapp run-server
## run-server: Run the API server only (without building webapp)
.PHONY: run-server
run-server: ### Runs the API server only (without building webapp)
$(info: Make: Running server)
CGO_ENABLED=${CGO_ENABLED} go run ./cmd/${PROJECT_NAME}/*.go server --config devdata/config.yaml
.PHONY: run-quick
run-quick: build-webapp run-server ### Builds the webapp and runs the server
run-server:
CGO_ENABLED=$(CGO_ENABLED) go run ./cmd/$(PROJECT_NAME)/*.go server --config devdata/config.yaml
## run-webapp: Run the webapp in dev mode with hot-reload
.PHONY: run-webapp
run-webapp: ### Runs the webapp in dev mode with hot-reload
$(info: Make: Running webapp dev server)
run-webapp:
@which bun > /dev/null || (echo "Error: Bun is not installed. Install it from https://bun.sh" && exit 1)
@cd webapp && bun --bun node_modules/.bin/vite --mode development
.PHONY: run
run: ### Executes the project build locally
@make build
${BUILDS_PATH}/${PROJECT_NAME}
.PHONY: build-docker
build-docker: build-webapp ### Build Docker image locally using goreleaser snapshot
$(info: Make: Building Docker image)
@which goreleaser > /dev/null || (echo "Error: goreleaser is not installed. Install it from https://goreleaser.com" && exit 1)
@which docker > /dev/null || (echo "Error: docker is not installed" && exit 1)
@goreleaser --clean --snapshot --skip=publish
## format: Format code and tidy modules
.PHONY: format
format: ### Executes the formatting pipeline on the project
$(info: Make: Format)
@go fmt ./...
@go mod tidy
format:
go fmt ./...
go mod tidy
## ci-lint: Run golangci-lint (installs if missing)
.PHONY: ci-lint
ci-lint: ### Check the project for errors
$(info: Make: Lint)
@go install github.com/golangci/golangci-lint/cmd/golangci-lint@${GOLANGCI_LINT_VERSION}
@golangci-lint run ./...
ci-lint:
@which golangci-lint > /dev/null 2>&1 || go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION)
golangci-lint run ./...
## lint: Run all linters
.PHONY: lint
lint: lint-server lint-webapp ### Check the project for errors
$(info: Make: Lint)
lint: lint-server lint-webapp
## lint-server: Check the server for linting errors
.PHONY: lint-server
lint-server: ### Check the server for linting errors
$(info: Make: Lint server)
lint-server:
@golangci-lint run ./...
## lint-webapp: Check the webapp for linting errors
.PHONY: lint-webapp
lint-webapp: ### Check the webapp for linting errors
$(info: Make: Lint webapp)
lint-webapp:
@which bun > /dev/null || (echo "Error: Bun is not installed. Install it from https://bun.sh" && exit 1)
@cd webapp && bun --bun node_modules/.bin/eslint .
## test: Run tests with coverage
.PHONY: test
test: ### Runs the test suite
$(info: Make: Test)
CGO_ENABLED=1 go test ${TEST_OPTIONS} -timeout=${TEST_TIMEOUT} ${SOURCE_FILES}
@echo "Generating HTML coverage report..."
@go tool cover -html=coverage.out -o coverage.html
@echo "✅ Coverage report generated: coverage.html"
test:
CGO_ENABLED=1 go test $(TEST_OPTIONS) -timeout=$(TEST_TIMEOUT) ./...
## e2e: Run end-to-end integration tests
.PHONY: e2e
e2e: ### Run end-to-end integration tests
$(info: Make: E2E Tests)
e2e:
@./scripts/e2e.sh
## e2e-install: Install Playwright browsers
.PHONY: e2e-install
e2e-install: ### Install Playwright browsers
$(info: Make: Installing E2E dependencies)
e2e-install:
@go run github.com/playwright-community/playwright-go/cmd/playwright@latest install --with-deps chromium

View file

@ -1,16 +1,10 @@
services:
hako:
# Note: Before running docker-compose, ensure the Linux binary is prepared:
# Run: make prepare-docker-compose
# Or: make build-docker (which builds via goreleaser)
# Note: Build the Docker image via goreleaser:
# Run: make build-docker
build:
context: .
dockerfile: Containerfile
platforms:
- linux/amd64
args:
TARGETPLATFORM: dist/hako_linux_amd64_v1
platform: linux/amd64
container_name: hako
restart: unless-stopped
ports:

View file

@ -1,4 +1,4 @@
FROM alpine:3.20
FROM alpine:3.23
RUN apk add --no-cache poppler-utils ca-certificates tzdata && \
addgroup -g 1000 hako && \

View file

@ -53,16 +53,23 @@ func StartHakoContainer(ctx context.Context, t *testing.T) (testcontainers.Conta
os.Remove(binaryPath)
})
// Create container request
// 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",
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

View file

@ -178,10 +178,12 @@ func (h *TestHelper) GetURL() string {
return h.page.URL()
}
// IsVisible checks if an element matching the selector is visible.
// IsVisible checks if any element matching the selector is visible.
// Uses .First() to avoid Playwright strict-mode errors when the selector
// resolves to multiple elements.
func (h *TestHelper) IsVisible(selector string) bool {
h.t.Helper()
visible, err := h.page.Locator(selector).IsVisible()
visible, err := h.page.Locator(selector).First().IsVisible()
if err != nil {
h.t.Logf("Error checking visibility of %s: %v", selector, err)
return false
@ -305,9 +307,18 @@ func (h *TestHelper) Logout() error {
}
// CreateLink creates a new link via the Quick Add form.
// Navigates to /home first to ensure the form is available even when called
// repeatedly (after the first call the user is redirected to /links).
func (h *TestHelper) CreateLink(url string) error {
h.t.Helper()
// Ensure we're on the home page where the Quick Add form lives.
if !strings.HasSuffix(h.GetURL(), "/home") {
if err := h.Navigate("/home"); err != nil {
return fmt.Errorf("failed to navigate to home: %w", err)
}
}
// Wait for URL input on home page
if err := h.WaitForSelector("input[name=\"url\"]", 5*time.Second); err != nil {
return fmt.Errorf("URL input not found: %w", err)

34
e2e/e2eutil/network.go Normal file
View file

@ -0,0 +1,34 @@
package e2eutil
import (
"context"
"fmt"
"sync"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/network"
)
// Shared network used by the test web server and Hako container so that
// Hako can fetch URLs from the test web server via Docker DNS.
var (
sharedNetwork *testcontainers.DockerNetwork
sharedNetworkErr error
sharedNetworkOnce sync.Once
)
// Network alias used by the test web server inside the shared network.
const testServerAlias = "hako-testserver"
// getSharedNetwork lazily creates a shared Docker network for the test process.
// Both StartTestWebServer and StartHakoContainer attach to it so containers
// can communicate by alias.
func getSharedNetwork(ctx context.Context) (*testcontainers.DockerNetwork, error) {
sharedNetworkOnce.Do(func() {
sharedNetwork, sharedNetworkErr = network.New(ctx)
})
if sharedNetworkErr != nil {
return nil, fmt.Errorf("failed to create shared network: %w", sharedNetworkErr)
}
return sharedNetwork, nil
}

View file

@ -7,13 +7,13 @@ import (
"testing"
"time"
"github.com/docker/go-connections/nat"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
)
// StartTestWebServer starts an nginx container with test content for E2E testing.
// It builds the test web server image and returns the container instance and base URL.
// The container is attached to the shared network with the testServerAlias hostname,
// and the returned URL uses that alias so the Hako container can fetch from it.
func StartTestWebServer(ctx context.Context, t *testing.T) (testcontainers.Container, string, error) {
t.Helper()
@ -23,7 +23,13 @@ func StartTestWebServer(ctx context.Context, t *testing.T) (testcontainers.Conta
return nil, "", fmt.Errorf("failed to get project root: %w", err)
}
// Create container request
// Ensure shared network exists so Hako can reach this 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", "testserver"),
@ -32,6 +38,10 @@ func StartTestWebServer(ctx context.Context, t *testing.T) (testcontainers.Conta
},
ExposedPorts: []string{"80/tcp"},
WaitingFor: wait.ForHTTP("/").WithStartupTimeout(10 * time.Second),
Networks: []string{nw.Name},
NetworkAliases: map[string][]string{
nw.Name: {testServerAlias},
},
}
// Start container
@ -51,21 +61,10 @@ func StartTestWebServer(ctx context.Context, t *testing.T) (testcontainers.Conta
}
})
// Get the mapped port
mappedPort, err := container.MappedPort(ctx, nat.Port("80/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("Test web server started at %s", baseURL)
// Use the network alias as the base URL so Hako can reach this server
// from inside its container via Docker DNS.
baseURL := fmt.Sprintf("http://%s", testServerAlias)
t.Logf("Test web server started at %s (network alias)", baseURL)
return container, baseURL, nil
}

View file

@ -6,6 +6,8 @@ replace github.com/fmartingr/hako => ../
require (
github.com/docker/go-connections v0.6.0
github.com/google/uuid v1.6.0
github.com/mattn/go-sqlite3 v1.14.41
github.com/playwright-community/playwright-go v0.5200.1
github.com/stretchr/testify v1.11.1
github.com/testcontainers/testcontainers-go v0.40.0
@ -34,7 +36,6 @@ require (
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-stack/stack v1.8.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/magiconair/properties v1.8.10 // indirect

View file

@ -68,6 +68,8 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-sqlite3 v1.14.41 h1:8p7Pwz5NHkEbWSqc/ygU4CBGubhFFkpgP9KwcdkAHNA=
github.com/mattn/go-sqlite3 v1.14.41/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ=
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=

View file

@ -265,7 +265,7 @@ func TestRules_RuleApplication(t *testing.T) {
helper.Screenshot("link-created-rule-test")
// Navigate to link details to see which archiver was used
err = helper.Click("a[href*='/links/']")
err = helper.Click(".link-item")
if err == nil {
time.Sleep(1 * time.Second)
helper.Screenshot("link-details-archiver-used")

View file

@ -53,13 +53,14 @@ func TestArchive_AutomaticCreation(t *testing.T) {
helper.Screenshot("link-created")
// Navigate to link details to see archive status
err = helper.Click("a[href*='/links/']")
err = helper.Click(".link-item")
require.NoError(t, err, "Failed to navigate to link details")
time.Sleep(1 * time.Second)
helper.Screenshot("link-details-archive")
// Verify archive section exists
helper.AssertVisible(".archive-status, .archive-history, [class*='archive']", "Archive information should be visible")
// Wait for archive processing to surface in the UI
err = helper.WaitForSelector(".archive-status, [class*='archive']", 30*time.Second)
require.NoError(t, err, "Archive information should appear")
helper.AssertVisible(".archive-status, [class*='archive']", "Archive information should be visible")
t.Log("Archive automatic creation test completed successfully")
}
@ -105,15 +106,15 @@ func TestArchive_ViewHistory(t *testing.T) {
time.Sleep(2 * time.Second)
// Navigate to link details
err = helper.Click("a[href*='/links/']")
err = helper.Click(".link-item")
require.NoError(t, err, "Failed to navigate to link details")
time.Sleep(1 * time.Second)
helper.Screenshot("archive-history")
// Verify archive history is visible
helper.AssertVisible(".archive-history, .archive-list, [class*='archive']", "Archive history should be visible")
// Wait for archive to appear in the UI
err = helper.WaitForSelector(".archive-status, [class*='archive']", 30*time.Second)
require.NoError(t, err, "Archive history should appear")
helper.AssertVisible(".archive-status, [class*='archive']", "Archive history should be visible")
// Wait for archive to process (with timeout)
t.Log("Waiting for archive to complete...")
time.Sleep(10 * time.Second)
helper.Screenshot("archive-after-wait")
@ -162,7 +163,7 @@ func TestArchive_StatusDisplay(t *testing.T) {
time.Sleep(2 * time.Second)
// Navigate to link details
err = helper.Click("a[href*='/links/']")
err = helper.Click(".link-item")
require.NoError(t, err, "Failed to navigate to link details")
time.Sleep(1 * time.Second)
helper.Screenshot("archive-status")
@ -222,7 +223,7 @@ func TestArchive_Rearchive(t *testing.T) {
time.Sleep(2 * time.Second)
// Navigate to link details
err = helper.Click("a[href*='/links/']")
err = helper.Click(".link-item")
require.NoError(t, err, "Failed to navigate to link details")
time.Sleep(1 * time.Second)
helper.Screenshot("before-rearchive")
@ -295,7 +296,7 @@ func TestArchive_ViewFiles(t *testing.T) {
time.Sleep(2 * time.Second)
// Navigate to link details
err = helper.Click("a[href*='/links/']")
err = helper.Click(".link-item")
require.NoError(t, err, "Failed to navigate to link details")
time.Sleep(1 * time.Second)
@ -356,7 +357,7 @@ func TestArchive_PreviewHTML(t *testing.T) {
time.Sleep(2 * time.Second)
// Navigate to link details
err = helper.Click("a[href*='/links/']")
err = helper.Click(".link-item")
require.NoError(t, err, "Failed to navigate to link details")
time.Sleep(1 * time.Second)

View file

@ -144,7 +144,7 @@ func TestError_ArchivalFailure(t *testing.T) {
helper.Screenshot("archival-failure-link-created")
// Navigate to link details
err = helper.Click("a[href*='/links/']")
err = helper.Click(".link-item")
if err == nil {
time.Sleep(5 * time.Second)
helper.Screenshot("archival-failure-details")

View file

@ -4,19 +4,19 @@ set -e
echo "🧪 Running Hako E2E Tests"
# Environment setup
export TESTCONTAINERS_RYUK_DISABLED=false
export TESTCONTAINERS_RYUK_DISABLED="${TESTCONTAINERS_RYUK_DISABLED:-true}"
export DOCKER_HOST="${DOCKER_HOST:-unix:///var/run/docker.sock}"
# Check Docker
if ! docker info > /dev/null 2>&1; then
echo "❌ Error: Docker is not running"
echo "❌ Error: Docker is not available (check DOCKER_HOST and socket mount)"
exit 1
fi
# Install Playwright if needed
if ! go run github.com/playwright-community/playwright-go/cmd/playwright@latest --help > /dev/null 2>&1; then
echo "📦 Installing Playwright browsers..."
go run github.com/playwright-community/playwright-go/cmd/playwright@latest install --with-deps chromium
(cd e2e && go run github.com/playwright-community/playwright-go/cmd/playwright@latest install chromium)
fi
# Build webapp (required for embed)

0
webapp/dist/.gitkeep vendored Normal file
View file

View file

@ -7,7 +7,7 @@ import (
"os"
)
//go:embed dist
//go:embed all:dist
var embeddedWebappFS embed.FS
// GetFilesystem returns a HTTP filesystem with the embedded webapp files