Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b10e75dd6 | ||
|
|
8b3a6e4666 | ||
|
|
d2b1d1e8cc | ||
|
|
ccd9cd9c94 | ||
|
|
f310eeba9b | ||
|
|
f956aee811 | ||
|
|
2146dd3f46 | ||
|
|
393cda8785 | ||
|
|
4fbf5c6b53 | ||
|
|
cdd2202839 |
@@ -0,0 +1,69 @@
|
|||||||
|
name: Claude Assistant for Gitea
|
||||||
|
|
||||||
|
on:
|
||||||
|
# Trigger on issue comments (works on both issues and pull requests in Gitea)
|
||||||
|
issue_comment:
|
||||||
|
types: [created]
|
||||||
|
# Trigger on issues being opened or assigned
|
||||||
|
issues:
|
||||||
|
types: [opened, assigned]
|
||||||
|
# Note: pull_request_review_comment has limited support in Gitea
|
||||||
|
# Use issue_comment instead which covers PR comments
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
claude-assistant:
|
||||||
|
# Basic trigger detection - check for @claude in comments or issue body
|
||||||
|
if: |
|
||||||
|
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
|
||||||
|
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || github.event.action == 'assigned'))
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
issues: write
|
||||||
|
# Note: Gitea Actions may not require id-token: write for basic functionality
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup golang environment
|
||||||
|
uses: actions/setup-go@v7
|
||||||
|
with:
|
||||||
|
go-version: 'stable'
|
||||||
|
check-latest: true
|
||||||
|
token: ${{ gitea.token }}
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
- name: Run Claude Assistant
|
||||||
|
uses: markwylde/claude-code-gitea-action@gitea
|
||||||
|
with:
|
||||||
|
allowed_tools: |
|
||||||
|
penpot-self-hosted__execute_code
|
||||||
|
penpot-self-hosted__high_level_overview
|
||||||
|
penpot-self-hosted__penpot_api_info
|
||||||
|
penpot-self-hosted__export_shape
|
||||||
|
penpot_self_hosted__execute_code
|
||||||
|
penpot_self_hosted__high_level_overview
|
||||||
|
penpot_self_hosted__penpot_api_info
|
||||||
|
penpot_self_hosted__export_shape
|
||||||
|
gitea_api_url: "https://gitea.inventory-plus-plus.com/api/v1"
|
||||||
|
gitea_token: ${{ secrets.GITEA_TOKEN }} # Use standard workflow token
|
||||||
|
# anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||||
|
# Prefer claude_code_oauth_token over anthropic_api_key (cheaper!)
|
||||||
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||||
|
timeout_minutes: "60"
|
||||||
|
trigger_phrase: "@claude"
|
||||||
|
assignee_trigger: "@claude"
|
||||||
|
# Needed due to bug: https://github.com/anthropics/claude-code-action/issues/1416
|
||||||
|
model: "claude-sonnet-5"
|
||||||
|
# Optional: Customize for Gitea environment
|
||||||
|
custom_instructions: |
|
||||||
|
You are working in a Gitea environment. Be aware that:
|
||||||
|
- Some GitHub Actions features may behave differently
|
||||||
|
- Focus on core functionality and avoid advanced GitHub-specific features
|
||||||
|
- Use standard git operations when possible
|
||||||
|
env:
|
||||||
|
GITEA_SERVER_URL: "https://gitea.inventory-plus-plus.com"
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"penpot-self-hosted": {
|
||||||
|
"description": "Set here, even though set globally, so it is also available for the gitea-claude action bot",
|
||||||
|
"type": "http",
|
||||||
|
"url": "https://penpot.inventory-plus-plus.com/mcp/stream?userToken=${PENPOT_MCP_USER_TOKEN}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -249,6 +249,27 @@ against the live API doesn't exist yet.
|
|||||||
you test multiple identities/accounts side by side. Never enable this
|
you test multiple identities/accounts side by side. Never enable this
|
||||||
outside local development.
|
outside local development.
|
||||||
|
|
||||||
|
## Gitea
|
||||||
|
|
||||||
|
- This project is hosted on a self-hosted **Gitea** instance at
|
||||||
|
`gitea.inventory-plus-plus.com` (repo:
|
||||||
|
`angel/inventory-plus-plus`), not GitHub - the `origin` remote points at
|
||||||
|
it over SSH. Issues, pull requests, wiki, and CI (Gitea Actions - see the
|
||||||
|
Tests badge at the top of `README.md`) all live there rather than on
|
||||||
|
GitHub, even though the tooling/workflow (Actions YAML, PR-based review)
|
||||||
|
looks GitHub-shaped.
|
||||||
|
- A Gitea MCP server is available in agent sessions (tools prefixed
|
||||||
|
`mcp__angel__...` - e.g. `issue_read`/`issue_write`,
|
||||||
|
`pull_request_read`/`pull_request_write`,
|
||||||
|
`list_pull_requests`/`list_issues`, `list_branches`, `wiki_read`/
|
||||||
|
`wiki_write`) for reading/managing issues, PRs, branches, releases, and
|
||||||
|
the wiki without shelling out to `git`/`gh`. There is no GitHub CLI
|
||||||
|
(`gh`) equivalent here - use these MCP tools or `git` directly instead.
|
||||||
|
- The Gitea MCP server has **no Projects API** - it cannot read or modify
|
||||||
|
Gitea Project boards. Don't attempt to automate Project-board changes
|
||||||
|
(e.g. moving an issue between columns) through it; that has to be done
|
||||||
|
manually in the Gitea UI.
|
||||||
|
|
||||||
## Commit conventions
|
## Commit conventions
|
||||||
|
|
||||||
- Commit messages: imperative mood subject line, no period, body explains
|
- Commit messages: imperative mood subject line, no period, body explains
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ Documentation is split up rather than kept in one big doc - start here:
|
|||||||
- **[`domains/platforms/etsy/COMPLIANCE.md`](./domains/platforms/etsy/COMPLIANCE.md)** - Etsy API usage obligations to keep satisfied when touching the Etsy integration.
|
- **[`domains/platforms/etsy/COMPLIANCE.md`](./domains/platforms/etsy/COMPLIANCE.md)** - Etsy API usage obligations to keep satisfied when touching the Etsy integration.
|
||||||
- **[`diagrams/`](./diagrams)** - architecture and schema diagrams, linked from the Diagrams section below.
|
- **[`diagrams/`](./diagrams)** - architecture and schema diagrams, linked from the Diagrams section below.
|
||||||
|
|
||||||
|
This project is hosted on a self-hosted [Gitea](https://gitea.inventory-plus-plus.com/angel/inventory-plus-plus) instance (not GitHub) - issues, pull requests, CI (the Tests badge above), and the wiki all live there. See `AGENTS.md`'s Gitea section for details, including the Gitea MCP server tools available to AI agents.
|
||||||
|
|
||||||
|
|
||||||
## Deploying
|
## Deploying
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,128 @@
|
|||||||
|
package ui_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"ruben/inventory2/domains/accounts"
|
||||||
|
"ruben/inventory2/domains/authentication"
|
||||||
|
etsy_platform "ruben/inventory2/domains/platforms/etsy"
|
||||||
|
"ruben/inventory2/domains/raw_events"
|
||||||
|
"ruben/inventory2/domains/reports"
|
||||||
|
"ruben/inventory2/internal/testdb"
|
||||||
|
"ruben/inventory2/server/auth"
|
||||||
|
"ruben/inventory2/server/response"
|
||||||
|
"ruben/inventory2/server/ui"
|
||||||
|
)
|
||||||
|
|
||||||
|
// update regenerates the golden files under testdata/golden from the
|
||||||
|
// current rendered output, instead of comparing against them. Review the
|
||||||
|
// diff like any other golden file before committing it, e.g.:
|
||||||
|
//
|
||||||
|
// TEST_DATABASE_URL=... go test ./server/ui/... -run TestServeTemplate_Golden -update
|
||||||
|
var update = flag.Bool("update", false, "update golden files in testdata/golden instead of comparing against them")
|
||||||
|
|
||||||
|
// TestServeTemplate_Golden is a raw-HTML snapshot/regression test: it
|
||||||
|
// exercises server/ui's real routing + templating path end-to-end (real
|
||||||
|
// templates, real Store types backed by a real Postgres connection, the
|
||||||
|
// same response.HandleResponses/HandleErrors middleware production uses -
|
||||||
|
// nothing about the render path is mocked) and diffs the rendered HTML
|
||||||
|
// against a committed golden file, so an unintentional template/markup
|
||||||
|
// change shows up as a reviewable diff instead of shipping silently.
|
||||||
|
//
|
||||||
|
// This is the "raw HTML snapshot" layer described in issue #33's UI-testing
|
||||||
|
// proposal. A browser-screenshot layer (e.g. chromedp, for catching CSS/
|
||||||
|
// layout regressions raw HTML can't) and Penpot-design-fidelity comparisons
|
||||||
|
// are separate, not-yet-implemented layers of that same proposal.
|
||||||
|
//
|
||||||
|
// Only covers logged-out pages for now (no seeded oauth session/cookie);
|
||||||
|
// extend the tests table below using testdb.SeedOAuthSession plus an
|
||||||
|
// "access_token" cookie on the request to cover authenticated pages.
|
||||||
|
//
|
||||||
|
// Like other integration tests in this repo, it's skipped (not failed) if
|
||||||
|
// TEST_DATABASE_URL is unset. If a golden file hasn't been generated yet,
|
||||||
|
// the individual case is also skipped (not failed) rather than breaking
|
||||||
|
// the suite - run with -update once, review the generated file, and commit
|
||||||
|
// it to turn that case into a real regression check.
|
||||||
|
func TestServeTemplate_Golden(t *testing.T) {
|
||||||
|
pool := testdb.Pool(t)
|
||||||
|
logger := testdb.Logger()
|
||||||
|
|
||||||
|
accts := accounts.NewStore(logger, pool)
|
||||||
|
rawEvents := raw_events.NewStore(logger, pool)
|
||||||
|
reps := reports.NewStore(logger, pool, accts)
|
||||||
|
etsy := etsy_platform.NewPlatform(
|
||||||
|
logger,
|
||||||
|
func(acctID int64) string { return "" },
|
||||||
|
"", "",
|
||||||
|
pool,
|
||||||
|
)
|
||||||
|
|
||||||
|
authr := authentication.NewDev(pool, logger)
|
||||||
|
authM := auth.NewService(logger, authr, accts)
|
||||||
|
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
router := gin.New()
|
||||||
|
router.Use(
|
||||||
|
authM.Identify,
|
||||||
|
response.HandleResponses,
|
||||||
|
response.HandleErrors,
|
||||||
|
)
|
||||||
|
|
||||||
|
ui.Routes(
|
||||||
|
logger,
|
||||||
|
router.Group("/ui"),
|
||||||
|
"/ui",
|
||||||
|
rawEvents,
|
||||||
|
accts,
|
||||||
|
reps,
|
||||||
|
etsy,
|
||||||
|
authM.Authenticate(),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
path string
|
||||||
|
wantStatus int
|
||||||
|
}{
|
||||||
|
{name: "home-logged-out", path: "/ui", wantStatus: http.StatusOK},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
req := httptest.NewRequest(http.MethodGet, tt.path, nil)
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
|
||||||
|
require.Equal(t, tt.wantStatus, rec.Code, "unexpected status for %s", tt.path)
|
||||||
|
|
||||||
|
got := rec.Body.Bytes()
|
||||||
|
golden := filepath.Join("testdata", "golden", tt.name+".html")
|
||||||
|
|
||||||
|
if *update {
|
||||||
|
require.NoError(t, os.MkdirAll(filepath.Dir(golden), 0o755))
|
||||||
|
require.NoError(t, os.WriteFile(golden, got, 0o644))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
want, err := os.ReadFile(golden)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
t.Skipf(
|
||||||
|
"golden file %s does not exist yet; generate it once via `go test ./server/ui/... -run TestServeTemplate_Golden -update` (requires TEST_DATABASE_URL), then review and commit it",
|
||||||
|
golden,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
require.NoError(t, err, "reading golden file %s", golden)
|
||||||
|
|
||||||
|
require.Equal(t, string(want), string(got), "rendered output for %s no longer matches %s; if this change is intentional, regenerate with -update", tt.path, golden)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user