ci: migrate from Woodpecker CI to Forgejo Actions #1
22 changed files with 293 additions and 279 deletions
7
.dockerignore
Normal file
7
.dockerignore
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
hako
|
||||||
|
hako.db
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
devdata/
|
||||||
|
.git/
|
||||||
|
webapp/node_modules/
|
||||||
87
.github/workflows/ci.yml
vendored
Normal file
87
.github/workflows/ci.yml
vendored
Normal 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
33
.github/workflows/release.yml
vendored
Normal 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 }}
|
||||||
|
|
@ -57,6 +57,7 @@ dockers_v2:
|
||||||
- "git.nakama.town/fmartingr/hako"
|
- "git.nakama.town/fmartingr/hako"
|
||||||
tags:
|
tags:
|
||||||
- "{{ .Version }}"
|
- "{{ .Version }}"
|
||||||
|
- latest
|
||||||
dockerfile: Containerfile
|
dockerfile: Containerfile
|
||||||
platforms:
|
platforms:
|
||||||
- linux/amd64
|
- linux/amd64
|
||||||
|
|
@ -65,7 +66,7 @@ dockers_v2:
|
||||||
|
|
||||||
nfpms:
|
nfpms:
|
||||||
- maintainer: Felipe Martin <me@fmartingr.com>
|
- 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
|
homepage: https://git.nakama.town/fmartingr/hako
|
||||||
license: AGPL-3.0
|
license: AGPL-3.0
|
||||||
formats:
|
formats:
|
||||||
|
|
@ -119,4 +120,7 @@ changelog:
|
||||||
- "^chore\\(deps\\):"
|
- "^chore\\(deps\\):"
|
||||||
|
|
||||||
release:
|
release:
|
||||||
|
gitea:
|
||||||
|
owner: fmartingr
|
||||||
|
name: hako
|
||||||
prerelease: auto
|
prerelease: auto
|
||||||
|
|
|
||||||
|
|
@ -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]
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -1,36 +1,20 @@
|
||||||
# This file is used directly by the goreleaser build
|
FROM alpine:3.23
|
||||||
# It is used to build the final container image
|
|
||||||
# To build the image locally use make build-docker
|
|
||||||
|
|
||||||
# Runtime image
|
|
||||||
FROM alpine:3.20
|
|
||||||
LABEL maintainer="Felipe Martin <me@fmartingr.com>"
|
LABEL maintainer="Felipe Martin <me@fmartingr.com>"
|
||||||
LABEL org.opencontainers.image.source="https://git.nakama.town/fmartingr/hako"
|
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 \
|
RUN apk add --no-cache \
|
||||||
poppler-utils \
|
poppler-utils \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
tzdata \
|
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 -u 1000 -G hako -s /bin/sh -D hako
|
||||||
RUN addgroup -g 1000 hako && \
|
|
||||||
adduser -D -u 1000 -G hako hako
|
|
||||||
|
|
||||||
WORKDIR /app
|
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
|
USER hako
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/local/bin/hako"]
|
ENTRYPOINT ["/usr/local/bin/hako"]
|
||||||
|
|
|
||||||
162
Makefile
162
Makefile
|
|
@ -1,156 +1,104 @@
|
||||||
PROJECT_NAME := hako
|
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
|
CGO_ENABLED := 0
|
||||||
|
|
||||||
BUILDS_PATH := ./dist
|
DIST_PATH := ./dist
|
||||||
FROM_MAKEFILE := y
|
TEST_OPTIONS := -v -failfast -race -bench=. -benchtime=100000x -cover -coverprofile=coverage.out
|
||||||
|
TEST_TIMEOUT := 5m
|
||||||
|
|
||||||
CONTAINERFILE_NAME := Containerfile
|
GOLANGCI_LINT_VERSION := v1.64.5
|
||||||
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}
|
|
||||||
|
|
||||||
BUILDX_PLATFORMS := linux/amd64,linux/arm64,linux/arm/v7
|
.DEFAULT_GOAL := help
|
||||||
|
|
||||||
export PROJECT_NAME
|
## help: Display this help message
|
||||||
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
|
|
||||||
.PHONY: help
|
.PHONY: help
|
||||||
help: ### this screen. Keep it first target to be default
|
help:
|
||||||
ifeq ($(UNAME), Linux)
|
@echo "Available targets:"
|
||||||
@grep -P '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | \
|
@grep -E '^## ' $(MAKEFILE_LIST) | sed 's/## / /' | 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
|
|
||||||
|
|
||||||
|
## clean: Clean test cache, build files
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean: ### clean test cache, build files
|
clean:
|
||||||
$(info: Make: Clean)
|
@rm -rf $(DIST_PATH)
|
||||||
@rm -rf ${BUILDS_PATH}
|
|
||||||
@rm -rf webapp/dist
|
@rm -rf webapp/dist
|
||||||
@rm -rf webapp/node_modules
|
@rm -rf webapp/node_modules
|
||||||
@rm -f webapp/bun.lockb
|
@rm -f webapp/bun.lockb
|
||||||
@go clean ${CLEAN_OPTIONS}
|
@go clean -modcache -testcache
|
||||||
@find devdata -mindepth 1 ! -name config.yaml -delete 2>/dev/null || true
|
|
||||||
@-docker buildx rm ${PROJECT_NAME}_builder
|
|
||||||
|
|
||||||
|
## clean-devdata: Clear devdata database and files (keeps config.yaml)
|
||||||
.PHONY: clean-devdata
|
.PHONY: clean-devdata
|
||||||
clean-devdata: ### Clear devdata database and files (keeps config.yaml)
|
clean-devdata:
|
||||||
$(info: Make: Cleaning devdata)
|
|
||||||
@rm -rf devdata/data
|
@rm -rf devdata/data
|
||||||
@rm -f devdata/hako.db devdata/hako.db-shm devdata/hako.db-wal
|
@rm -f devdata/hako.db devdata/hako.db-shm devdata/hako.db-wal
|
||||||
|
|
||||||
|
## build: Build project using goreleaser (snapshot)
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
build: build-webapp ### builds the project for the setup os/arch combinations
|
build: build-webapp
|
||||||
$(info: Make: Build)
|
goreleaser build --snapshot --clean
|
||||||
@goreleaser --clean --snapshot
|
|
||||||
|
|
||||||
|
## build-webapp: Build the webapp
|
||||||
.PHONY: build-webapp
|
.PHONY: build-webapp
|
||||||
build-webapp: ### Build the webapp
|
build-webapp:
|
||||||
$(info: Make: Building webapp)
|
|
||||||
@which bun > /dev/null || (echo "Error: Bun is not installed. Install it from https://bun.sh" && exit 1)
|
@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
|
@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
|
.PHONY: run-server
|
||||||
run-server: ### Runs the API server only (without building webapp)
|
run-server:
|
||||||
$(info: Make: Running server)
|
CGO_ENABLED=$(CGO_ENABLED) go run ./cmd/$(PROJECT_NAME)/*.go server --config devdata/config.yaml
|
||||||
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-webapp: Run the webapp in dev mode with hot-reload
|
||||||
.PHONY: run-webapp
|
.PHONY: run-webapp
|
||||||
run-webapp: ### Runs the webapp in dev mode with hot-reload
|
run-webapp:
|
||||||
$(info: Make: Running webapp dev server)
|
|
||||||
@which bun > /dev/null || (echo "Error: Bun is not installed. Install it from https://bun.sh" && exit 1)
|
@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
|
@cd webapp && bun --bun node_modules/.bin/vite --mode development
|
||||||
|
|
||||||
.PHONY: run
|
## format: Format code and tidy modules
|
||||||
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
|
|
||||||
|
|
||||||
.PHONY: format
|
.PHONY: format
|
||||||
format: ### Executes the formatting pipeline on the project
|
format:
|
||||||
$(info: Make: Format)
|
go fmt ./...
|
||||||
@go fmt ./...
|
go mod tidy
|
||||||
@go mod tidy
|
|
||||||
|
|
||||||
|
## ci-lint: Run golangci-lint (installs if missing)
|
||||||
.PHONY: ci-lint
|
.PHONY: ci-lint
|
||||||
ci-lint: ### Check the project for errors
|
ci-lint:
|
||||||
$(info: Make: Lint)
|
@which golangci-lint > /dev/null 2>&1 || go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION)
|
||||||
@go install github.com/golangci/golangci-lint/cmd/golangci-lint@${GOLANGCI_LINT_VERSION}
|
golangci-lint run ./...
|
||||||
@golangci-lint run ./...
|
|
||||||
|
|
||||||
|
## lint: Run all linters
|
||||||
.PHONY: lint
|
.PHONY: lint
|
||||||
lint: lint-server lint-webapp ### Check the project for errors
|
lint: lint-server lint-webapp
|
||||||
$(info: Make: Lint)
|
|
||||||
|
|
||||||
|
## lint-server: Check the server for linting errors
|
||||||
.PHONY: lint-server
|
.PHONY: lint-server
|
||||||
lint-server: ### Check the server for linting errors
|
lint-server:
|
||||||
$(info: Make: Lint server)
|
|
||||||
@golangci-lint run ./...
|
@golangci-lint run ./...
|
||||||
|
|
||||||
|
## lint-webapp: Check the webapp for linting errors
|
||||||
.PHONY: lint-webapp
|
.PHONY: lint-webapp
|
||||||
lint-webapp: ### Check the webapp for linting errors
|
lint-webapp:
|
||||||
$(info: Make: Lint webapp)
|
|
||||||
@which bun > /dev/null || (echo "Error: Bun is not installed. Install it from https://bun.sh" && exit 1)
|
@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 .
|
@cd webapp && bun --bun node_modules/.bin/eslint .
|
||||||
|
|
||||||
|
## test: Run tests with coverage
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test: ### Runs the test suite
|
test:
|
||||||
$(info: Make: Test)
|
CGO_ENABLED=1 go test $(TEST_OPTIONS) -timeout=$(TEST_TIMEOUT) ./...
|
||||||
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"
|
|
||||||
|
|
||||||
|
## e2e: Run end-to-end integration tests
|
||||||
.PHONY: e2e
|
.PHONY: e2e
|
||||||
e2e: ### Run end-to-end integration tests
|
e2e:
|
||||||
$(info: Make: E2E Tests)
|
|
||||||
@./scripts/e2e.sh
|
@./scripts/e2e.sh
|
||||||
|
|
||||||
|
## e2e-install: Install Playwright browsers
|
||||||
.PHONY: e2e-install
|
.PHONY: e2e-install
|
||||||
e2e-install: ### Install Playwright browsers
|
e2e-install:
|
||||||
$(info: Make: Installing E2E dependencies)
|
|
||||||
@go run github.com/playwright-community/playwright-go/cmd/playwright@latest install --with-deps chromium
|
@go run github.com/playwright-community/playwright-go/cmd/playwright@latest install --with-deps chromium
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,10 @@
|
||||||
services:
|
services:
|
||||||
hako:
|
hako:
|
||||||
# Note: Before running docker-compose, ensure the Linux binary is prepared:
|
# Note: Build the Docker image via goreleaser:
|
||||||
# Run: make prepare-docker-compose
|
# Run: make build-docker
|
||||||
# Or: make build-docker (which builds via goreleaser)
|
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Containerfile
|
dockerfile: Containerfile
|
||||||
platforms:
|
|
||||||
- linux/amd64
|
|
||||||
args:
|
|
||||||
TARGETPLATFORM: dist/hako_linux_amd64_v1
|
|
||||||
platform: linux/amd64
|
|
||||||
container_name: hako
|
container_name: hako
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
FROM alpine:3.20
|
FROM alpine:3.23
|
||||||
|
|
||||||
RUN apk add --no-cache poppler-utils ca-certificates tzdata && \
|
RUN apk add --no-cache poppler-utils ca-certificates tzdata && \
|
||||||
addgroup -g 1000 hako && \
|
addgroup -g 1000 hako && \
|
||||||
|
|
|
||||||
|
|
@ -53,16 +53,23 @@ func StartHakoContainer(ctx context.Context, t *testing.T) (testcontainers.Conta
|
||||||
os.Remove(binaryPath)
|
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{
|
req := testcontainers.ContainerRequest{
|
||||||
FromDockerfile: testcontainers.FromDockerfile{
|
FromDockerfile: testcontainers.FromDockerfile{
|
||||||
Context: filepath.Join(projectRoot, "e2e"),
|
Context: filepath.Join(projectRoot, "e2e"),
|
||||||
Dockerfile: "Dockerfile.e2e",
|
Dockerfile: "Dockerfile.e2e",
|
||||||
PrintBuildLog: true,
|
PrintBuildLog: true,
|
||||||
},
|
},
|
||||||
ExposedPorts: []string{"8080/tcp"},
|
ExposedPorts: []string{"8080/tcp"},
|
||||||
WaitingFor: wait.ForLog("Starting HTTP server").
|
WaitingFor: wait.ForLog("Starting HTTP server").
|
||||||
WithStartupTimeout(30 * time.Second),
|
WithStartupTimeout(30 * time.Second),
|
||||||
|
Networks: []string{nw.Name},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start container
|
// Start container
|
||||||
|
|
|
||||||
|
|
@ -178,10 +178,12 @@ func (h *TestHelper) GetURL() string {
|
||||||
return h.page.URL()
|
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 {
|
func (h *TestHelper) IsVisible(selector string) bool {
|
||||||
h.t.Helper()
|
h.t.Helper()
|
||||||
visible, err := h.page.Locator(selector).IsVisible()
|
visible, err := h.page.Locator(selector).First().IsVisible()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.t.Logf("Error checking visibility of %s: %v", selector, err)
|
h.t.Logf("Error checking visibility of %s: %v", selector, err)
|
||||||
return false
|
return false
|
||||||
|
|
@ -305,9 +307,18 @@ func (h *TestHelper) Logout() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateLink creates a new link via the Quick Add form.
|
// 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 {
|
func (h *TestHelper) CreateLink(url string) error {
|
||||||
h.t.Helper()
|
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
|
// Wait for URL input on home page
|
||||||
if err := h.WaitForSelector("input[name=\"url\"]", 5*time.Second); err != nil {
|
if err := h.WaitForSelector("input[name=\"url\"]", 5*time.Second); err != nil {
|
||||||
return fmt.Errorf("URL input not found: %w", err)
|
return fmt.Errorf("URL input not found: %w", err)
|
||||||
|
|
|
||||||
34
e2e/e2eutil/network.go
Normal file
34
e2e/e2eutil/network.go
Normal 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
|
||||||
|
}
|
||||||
|
|
@ -7,13 +7,13 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/go-connections/nat"
|
|
||||||
"github.com/testcontainers/testcontainers-go"
|
"github.com/testcontainers/testcontainers-go"
|
||||||
"github.com/testcontainers/testcontainers-go/wait"
|
"github.com/testcontainers/testcontainers-go/wait"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StartTestWebServer starts an nginx container with test content for E2E testing.
|
// 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) {
|
func StartTestWebServer(ctx context.Context, t *testing.T) (testcontainers.Container, string, error) {
|
||||||
t.Helper()
|
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)
|
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{
|
req := testcontainers.ContainerRequest{
|
||||||
FromDockerfile: testcontainers.FromDockerfile{
|
FromDockerfile: testcontainers.FromDockerfile{
|
||||||
Context: filepath.Join(projectRoot, "e2e", "testserver"),
|
Context: filepath.Join(projectRoot, "e2e", "testserver"),
|
||||||
|
|
@ -32,6 +38,10 @@ func StartTestWebServer(ctx context.Context, t *testing.T) (testcontainers.Conta
|
||||||
},
|
},
|
||||||
ExposedPorts: []string{"80/tcp"},
|
ExposedPorts: []string{"80/tcp"},
|
||||||
WaitingFor: wait.ForHTTP("/").WithStartupTimeout(10 * time.Second),
|
WaitingFor: wait.ForHTTP("/").WithStartupTimeout(10 * time.Second),
|
||||||
|
Networks: []string{nw.Name},
|
||||||
|
NetworkAliases: map[string][]string{
|
||||||
|
nw.Name: {testServerAlias},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start container
|
// Start container
|
||||||
|
|
@ -51,21 +61,10 @@ func StartTestWebServer(ctx context.Context, t *testing.T) (testcontainers.Conta
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Get the mapped port
|
// Use the network alias as the base URL so Hako can reach this server
|
||||||
mappedPort, err := container.MappedPort(ctx, nat.Port("80/tcp"))
|
// from inside its container via Docker DNS.
|
||||||
if err != nil {
|
baseURL := fmt.Sprintf("http://%s", testServerAlias)
|
||||||
return nil, "", fmt.Errorf("failed to get mapped port: %w", err)
|
t.Logf("Test web server started at %s (network alias)", baseURL)
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
|
|
||||||
return container, baseURL, nil
|
return container, baseURL, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ replace github.com/fmartingr/hako => ../
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/docker/go-connections v0.6.0
|
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/playwright-community/playwright-go v0.5200.1
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/testcontainers/testcontainers-go v0.40.0
|
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-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||||
github.com/go-stack/stack v1.8.1 // 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/klauspost/compress v1.18.0 // indirect
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||||
github.com/magiconair/properties v1.8.10 // indirect
|
github.com/magiconair/properties v1.8.10 // indirect
|
||||||
|
|
|
||||||
|
|
@ -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/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 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
|
||||||
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
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 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
|
||||||
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
|
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=
|
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||||
|
|
|
||||||
|
|
@ -265,7 +265,7 @@ func TestRules_RuleApplication(t *testing.T) {
|
||||||
helper.Screenshot("link-created-rule-test")
|
helper.Screenshot("link-created-rule-test")
|
||||||
|
|
||||||
// Navigate to link details to see which archiver was used
|
// Navigate to link details to see which archiver was used
|
||||||
err = helper.Click("a[href*='/links/']")
|
err = helper.Click(".link-item")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
helper.Screenshot("link-details-archiver-used")
|
helper.Screenshot("link-details-archiver-used")
|
||||||
|
|
|
||||||
|
|
@ -53,13 +53,14 @@ func TestArchive_AutomaticCreation(t *testing.T) {
|
||||||
helper.Screenshot("link-created")
|
helper.Screenshot("link-created")
|
||||||
|
|
||||||
// Navigate to link details to see archive status
|
// 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")
|
require.NoError(t, err, "Failed to navigate to link details")
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
helper.Screenshot("link-details-archive")
|
helper.Screenshot("link-details-archive")
|
||||||
|
|
||||||
// Verify archive section exists
|
// Wait for archive processing to surface in the UI
|
||||||
helper.AssertVisible(".archive-status, .archive-history, [class*='archive']", "Archive information should be visible")
|
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")
|
t.Log("Archive automatic creation test completed successfully")
|
||||||
}
|
}
|
||||||
|
|
@ -105,15 +106,15 @@ func TestArchive_ViewHistory(t *testing.T) {
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
|
|
||||||
// Navigate to link details
|
// 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")
|
require.NoError(t, err, "Failed to navigate to link details")
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
helper.Screenshot("archive-history")
|
helper.Screenshot("archive-history")
|
||||||
|
|
||||||
// Verify archive history is visible
|
// Wait for archive to appear in the UI
|
||||||
helper.AssertVisible(".archive-history, .archive-list, [class*='archive']", "Archive history should be visible")
|
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...")
|
t.Log("Waiting for archive to complete...")
|
||||||
time.Sleep(10 * time.Second)
|
time.Sleep(10 * time.Second)
|
||||||
helper.Screenshot("archive-after-wait")
|
helper.Screenshot("archive-after-wait")
|
||||||
|
|
@ -162,7 +163,7 @@ func TestArchive_StatusDisplay(t *testing.T) {
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
|
|
||||||
// Navigate to link details
|
// 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")
|
require.NoError(t, err, "Failed to navigate to link details")
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
helper.Screenshot("archive-status")
|
helper.Screenshot("archive-status")
|
||||||
|
|
@ -222,7 +223,7 @@ func TestArchive_Rearchive(t *testing.T) {
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
|
|
||||||
// Navigate to link details
|
// 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")
|
require.NoError(t, err, "Failed to navigate to link details")
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
helper.Screenshot("before-rearchive")
|
helper.Screenshot("before-rearchive")
|
||||||
|
|
@ -295,7 +296,7 @@ func TestArchive_ViewFiles(t *testing.T) {
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
|
|
||||||
// Navigate to link details
|
// 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")
|
require.NoError(t, err, "Failed to navigate to link details")
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
|
|
||||||
|
|
@ -356,7 +357,7 @@ func TestArchive_PreviewHTML(t *testing.T) {
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
|
|
||||||
// Navigate to link details
|
// 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")
|
require.NoError(t, err, "Failed to navigate to link details")
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -144,7 +144,7 @@ func TestError_ArchivalFailure(t *testing.T) {
|
||||||
helper.Screenshot("archival-failure-link-created")
|
helper.Screenshot("archival-failure-link-created")
|
||||||
|
|
||||||
// Navigate to link details
|
// Navigate to link details
|
||||||
err = helper.Click("a[href*='/links/']")
|
err = helper.Click(".link-item")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
time.Sleep(5 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
helper.Screenshot("archival-failure-details")
|
helper.Screenshot("archival-failure-details")
|
||||||
|
|
|
||||||
|
|
@ -4,19 +4,19 @@ set -e
|
||||||
echo "🧪 Running Hako E2E Tests"
|
echo "🧪 Running Hako E2E Tests"
|
||||||
|
|
||||||
# Environment setup
|
# 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}"
|
export DOCKER_HOST="${DOCKER_HOST:-unix:///var/run/docker.sock}"
|
||||||
|
|
||||||
# Check Docker
|
# Check Docker
|
||||||
if ! docker info > /dev/null 2>&1; then
|
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
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Install Playwright if needed
|
# Install Playwright if needed
|
||||||
if ! go run github.com/playwright-community/playwright-go/cmd/playwright@latest --help > /dev/null 2>&1; then
|
if ! go run github.com/playwright-community/playwright-go/cmd/playwright@latest --help > /dev/null 2>&1; then
|
||||||
echo "📦 Installing Playwright browsers..."
|
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
|
fi
|
||||||
|
|
||||||
# Build webapp (required for embed)
|
# Build webapp (required for embed)
|
||||||
|
|
|
||||||
0
webapp/dist/.gitkeep
vendored
Normal file
0
webapp/dist/.gitkeep
vendored
Normal file
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed dist
|
//go:embed all:dist
|
||||||
var embeddedWebappFS embed.FS
|
var embeddedWebappFS embed.FS
|
||||||
|
|
||||||
// GetFilesystem returns a HTTP filesystem with the embedded webapp files
|
// GetFilesystem returns a HTTP filesystem with the embedded webapp files
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue