Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
72cb25111f | ||
|
|
38f222c931 | ||
|
|
e042b6d26d | ||
|
|
c85a32e770 | ||
|
|
b246c0e688 | ||
|
|
c7896cb3cc | ||
|
|
b79fb892c8 | ||
|
|
c315e3e003 |
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"brain": {
|
||||
"type": "http",
|
||||
"url": "https://brain-mcp.d-ma.be/mcp",
|
||||
"headers": {
|
||||
"Authorization": "Bearer ${BRAIN_MCP_TOKEN}"
|
||||
}
|
||||
},
|
||||
"gitea": {
|
||||
"type": "http",
|
||||
"url": "https://git-mcp.d-ma.be/mcp",
|
||||
"headers": {
|
||||
"Authorization": "Bearer ${GITEA_MCP_TOKEN}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ docs it indexes.
|
||||
3. `DECISIONS.md` — the ADRs. Decisions are settled here; do not re-litigate without a new ADR.
|
||||
4. `docs/architecture/architecture.md`, `docs/data-model.md`, `docs/use-cases/*.feature`.
|
||||
5. `docs/homelab-integration.md` — the concrete endpoints/conventions you'll need.
|
||||
6. `LANGUAGE.md` — the project vocabulary. Apply the caveman rubric before destructive operations.
|
||||
|
||||
## How to work in this repo
|
||||
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
# Vocabulary (hand-written pilot 2026-06 — will be generated by langgen in Phase 2)
|
||||
|
||||
| Term | Means | Never say |
|
||||
|---|---|---|
|
||||
| transcript | Captions-first text of a video; the source for summarizing. Shared store keyed by `(provider, video_id)`. | "subtitles file", "the audio" |
|
||||
| video | Per-user record of a seen video; the unit of work. Not globally deduped. | "the shared video" |
|
||||
| subscription | A watched channel on a user's connection that Tapir polls for new videos. | "feed", "follow" |
|
||||
| summary | A video's produced output: summary text + highlights + takeaways + AI provenance. | "transcript" |
|
||||
| highlight | A notable point pulled from a video (`Summary.Highlights`). | "takeaway" |
|
||||
| takeaway | An actionable conclusion from a video (`Summary.Takeaways`). | "highlight" |
|
||||
| sink | A delivery destination for a summary (`store`, `brain`). New one = new adapter. | "the database" |
|
||||
| AI router | Local-first chain: local Primary → local fallback → external worst-case. | "the API" |
|
||||
| BYO-AI fallback | User's own external AI key, used only when local fails; opt-in. Without it, content is never sent externally. | "the default AI" |
|
||||
| brain | Persistent homelab knowledge store; in Tapir, one optional HTTP sink (`brain_ingest`), not the filesystem package. | "the database", "the store" |
|
||||
| LiteLLM gateway | Local AI gateway (`koala:30401/v1`); the Primary in the AI router. | "piguard:4000", "koala:4000", "the cloud" |
|
||||
| connection | A connected video account a user authorizes via OAuth; subscriptions hang off it. | "login", "session" |
|
||||
|
||||
## Caveman rubric
|
||||
Before any HIGH/CRITICAL operation, output one line:
|
||||
`caveman: me <verb> <object>, not <excluded thing>`
|
||||
Valid iff: (1) only vocabulary terms + plain verbs, (2) a stranger could identify
|
||||
the exact operation, (3) names one thing explicitly NOT being done.
|
||||
|
||||
Examples:
|
||||
- `caveman: me delete user connection, not the shared transcript`
|
||||
- `caveman: me send transcript to BYO-AI fallback, not the LiteLLM gateway`
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/config"
|
||||
"git.d-ma.be/mathias/tapir/internal/config"
|
||||
)
|
||||
|
||||
// TestChatModelsReuseTheChainLocalFirst: the switcher offers the ADR-022 chain in
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"log/slog"
|
||||
"sync"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/runner"
|
||||
"git.d-ma.be/mathias/tapir/internal/runner"
|
||||
)
|
||||
|
||||
// discoveryRunner runs one user's discovery pass.
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/runner"
|
||||
"git.d-ma.be/mathias/tapir/internal/runner"
|
||||
)
|
||||
|
||||
// serialize must guarantee at most one discovery pass runs at a time, so a
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
)
|
||||
|
||||
// Env var names for the read-only CLI. DSN and user id are never hardcoded — the
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
)
|
||||
|
||||
func TestFormatListColumnsAndOrdering(t *testing.T) {
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ import (
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
)
|
||||
|
||||
// runList prints the user's stored summaries as a table, most recent first.
|
||||
|
||||
+9
-9
@@ -23,15 +23,15 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/secrets"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/youtube"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/auth"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/config"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/metrics"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/runner"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/web"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/web/oidc"
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/secrets"
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/youtube"
|
||||
"git.d-ma.be/mathias/tapir/internal/auth"
|
||||
"git.d-ma.be/mathias/tapir/internal/config"
|
||||
"git.d-ma.be/mathias/tapir/internal/metrics"
|
||||
"git.d-ma.be/mathias/tapir/internal/runner"
|
||||
"git.d-ma.be/mathias/tapir/internal/web"
|
||||
"git.d-ma.be/mathias/tapir/internal/web/oidc"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
+12
-12
@@ -5,18 +5,18 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/chat"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/llm"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/secrets"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/summarizer"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/youtube"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/config"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/domain"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/metrics"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/ports"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/usecase"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/web"
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/chat"
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/llm"
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/secrets"
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/summarizer"
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/youtube"
|
||||
"git.d-ma.be/mathias/tapir/internal/config"
|
||||
"git.d-ma.be/mathias/tapir/internal/domain"
|
||||
"git.d-ma.be/mathias/tapir/internal/metrics"
|
||||
"git.d-ma.be/mathias/tapir/internal/ports"
|
||||
"git.d-ma.be/mathias/tapir/internal/usecase"
|
||||
"git.d-ma.be/mathias/tapir/internal/web"
|
||||
)
|
||||
|
||||
// videoFetcher adapts the YouTube adapter to web.VideoFetcher for the paste flow
|
||||
|
||||
@@ -3,7 +3,7 @@ package main
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/config"
|
||||
"git.d-ma.be/mathias/tapir/internal/config"
|
||||
)
|
||||
|
||||
// TestBuildProcessorNilOnIncompleteConfig asserts the queue-only fallback: when a
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ import (
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
)
|
||||
|
||||
// gateThreshold is the Stage-0 gate (VISION/ADR-016): usage in >= 2 distinct
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
)
|
||||
|
||||
var testSince = time.Date(2026, 6, 11, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
@@ -6,13 +6,13 @@ import (
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/youtube"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/config"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/ports"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/runner"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/usecase"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/web"
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/youtube"
|
||||
"git.d-ma.be/mathias/tapir/internal/config"
|
||||
"git.d-ma.be/mathias/tapir/internal/ports"
|
||||
"git.d-ma.be/mathias/tapir/internal/runner"
|
||||
"git.d-ma.be/mathias/tapir/internal/usecase"
|
||||
"git.d-ma.be/mathias/tapir/internal/web"
|
||||
)
|
||||
|
||||
// buildUserRunner constructs a runner.Runner for one user, reusing the same
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/runner"
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"git.d-ma.be/mathias/tapir/internal/runner"
|
||||
)
|
||||
|
||||
func quietLog() *slog.Logger {
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
)
|
||||
|
||||
// runShow prints the full summary for one video: text, highlights, takeaways,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module gitea.d-ma.be/mathias/tapir
|
||||
module git.d-ma.be/mathias/tapir
|
||||
|
||||
go 1.26.1
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/metrics"
|
||||
"git.d-ma.be/mathias/tapir/internal/metrics"
|
||||
)
|
||||
|
||||
// Completer is the minimal LLM chat surface the Service needs. *llm.Client
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/ports"
|
||||
"git.d-ma.be/mathias/tapir/internal/ports"
|
||||
)
|
||||
|
||||
// FileStore is a SecretStore backed by a single 0600 JSON file mapping opaque
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/secrets"
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/secrets"
|
||||
)
|
||||
|
||||
func TestPutThenGet(t *testing.T) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
)
|
||||
|
||||
// seedUserRow inserts a bare users row (FK target for a connection) as the
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -3,6 +3,7 @@ package store_test
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
@@ -34,6 +35,30 @@ func fileMigrator(t *testing.T) *migrate.Migrate {
|
||||
return m
|
||||
}
|
||||
|
||||
// headVersion reports the current (HEAD) schema version so a test can restore
|
||||
// to it after stepping down, without hard-coding what HEAD is. Adding a
|
||||
// migration on top changes HEAD but no test that uses this needs editing.
|
||||
func headVersion(t *testing.T, m *migrate.Migrate) uint {
|
||||
t.Helper()
|
||||
v, dirty, err := m.Version()
|
||||
require.NoError(t, err)
|
||||
require.False(t, dirty, "schema must not be dirty")
|
||||
return v
|
||||
}
|
||||
|
||||
// migrateTo drives the schema to an exact version *by version number*, not by
|
||||
// step count. This is the whole point of the migrate-test design: a migration
|
||||
// added above the target does not shift any count here, so unrelated tests stay
|
||||
// green (see issue #8). ErrNoChange (already at that version) is not a failure.
|
||||
func migrateTo(t *testing.T, m *migrate.Migrate, version uint) {
|
||||
t.Helper()
|
||||
err := m.Migrate(version)
|
||||
if errors.Is(err, migrate.ErrNoChange) {
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// loginEventsExists reports whether the login_events relation is present.
|
||||
func loginEventsExists(t *testing.T) bool {
|
||||
t.Helper()
|
||||
@@ -53,25 +78,15 @@ func TestMigration010LoginEventsUpDown(t *testing.T) {
|
||||
require.True(t, loginEventsExists(t), "login_events must exist at latest migration")
|
||||
|
||||
m := fileMigrator(t)
|
||||
// 011..016 sit above 010; step them down first so 010 is exercised in isolation.
|
||||
require.NoError(t, m.Steps(-1), "down 016 drops channel_caption_state, login_events intact")
|
||||
require.True(t, loginEventsExists(t), "016 down leaves login_events intact")
|
||||
require.NoError(t, m.Steps(-1), "down 015 reshapes transcripts, login_events intact")
|
||||
require.True(t, loginEventsExists(t), "015 down leaves login_events intact")
|
||||
require.NoError(t, m.Steps(-1), "down 014 drops channel_title, login_events intact")
|
||||
require.True(t, loginEventsExists(t), "014 down leaves login_events intact")
|
||||
require.NoError(t, m.Steps(-1), "down 013 drops channel_errors, login_events intact")
|
||||
require.True(t, loginEventsExists(t), "013 down leaves login_events intact")
|
||||
require.NoError(t, m.Steps(-1), "down 012 is a no-op, login_events intact")
|
||||
require.True(t, loginEventsExists(t), "012 down leaves login_events intact")
|
||||
require.NoError(t, m.Steps(-1), "down 011 must not touch login_events")
|
||||
require.True(t, loginEventsExists(t), "011 down leaves login_events intact")
|
||||
head := headVersion(t, m)
|
||||
|
||||
require.NoError(t, m.Steps(-1), "down 010 must drop login_events")
|
||||
migrateTo(t, m, 9) // just below 010 — everything above steps down
|
||||
require.False(t, loginEventsExists(t), "login_events must be gone after the down migration")
|
||||
|
||||
require.NoError(t, m.Steps(7), "up must recreate 010 then re-apply 011..016")
|
||||
migrateTo(t, m, 10) // up 010
|
||||
require.True(t, loginEventsExists(t), "login_events must be restored after the up migration")
|
||||
|
||||
migrateTo(t, m, head) // restore to HEAD for sibling tests
|
||||
}
|
||||
|
||||
// autoSummarizeDefault reads the users.auto_summarize column default as text
|
||||
@@ -93,21 +108,15 @@ func TestMigration011AutoSummarizeDefaultUpDown(t *testing.T) {
|
||||
require.Equal(t, "true", autoSummarizeDefault(t), "011 sets the default to TRUE")
|
||||
|
||||
m := fileMigrator(t)
|
||||
require.NoError(t, m.Steps(-1), "down 016 drops channel_caption_state")
|
||||
require.NoError(t, m.Steps(-1), "down 015 reshapes transcripts")
|
||||
require.NoError(t, m.Steps(-1), "down 014 drops channel_title")
|
||||
require.NoError(t, m.Steps(-1), "down 013 drops channel_errors")
|
||||
require.NoError(t, m.Steps(-1), "down 012 is a no-op")
|
||||
require.NoError(t, m.Steps(-1), "down 011 reverts the column default")
|
||||
head := headVersion(t, m)
|
||||
|
||||
migrateTo(t, m, 10) // just below 011 — reverts the column default
|
||||
require.Equal(t, "false", autoSummarizeDefault(t), "default is FALSE after the down migration")
|
||||
|
||||
require.NoError(t, m.Steps(1), "up 011 re-applies the TRUE default")
|
||||
migrateTo(t, m, 11) // up 011 re-applies the TRUE default
|
||||
require.Equal(t, "true", autoSummarizeDefault(t))
|
||||
require.NoError(t, m.Steps(1), "up 012 runs clean (no FORCE RLS on fresh schema)")
|
||||
require.NoError(t, m.Steps(1), "up 013 creates channel_errors")
|
||||
require.NoError(t, m.Steps(1), "up 014 recreates channel_title")
|
||||
require.NoError(t, m.Steps(1), "up 015 reshapes transcripts to shared")
|
||||
require.NoError(t, m.Steps(1), "up 016 recreates channel_caption_state (HEAD)")
|
||||
|
||||
migrateTo(t, m, head) // restore to HEAD for sibling tests
|
||||
}
|
||||
|
||||
// channelTitleExists reports whether videos.channel_title is present.
|
||||
@@ -127,17 +136,15 @@ func TestMigration014VideoChannelTitleUpDown(t *testing.T) {
|
||||
require.True(t, channelTitleExists(t), "channel_title exists at latest migration")
|
||||
|
||||
m := fileMigrator(t)
|
||||
require.NoError(t, m.Steps(-1), "down 016 drops channel_caption_state, channel_title intact")
|
||||
require.True(t, channelTitleExists(t), "016 down leaves channel_title intact")
|
||||
require.NoError(t, m.Steps(-1), "down 015 reshapes transcripts, channel_title intact")
|
||||
require.True(t, channelTitleExists(t), "015 down leaves channel_title intact")
|
||||
require.NoError(t, m.Steps(-1), "down 014 must drop channel_title")
|
||||
head := headVersion(t, m)
|
||||
|
||||
migrateTo(t, m, 13) // just below 014 — drops channel_title
|
||||
require.False(t, channelTitleExists(t), "channel_title must be gone after the down migration")
|
||||
|
||||
require.NoError(t, m.Steps(1), "up 014 must recreate channel_title")
|
||||
migrateTo(t, m, 14) // up 014 recreates channel_title
|
||||
require.True(t, channelTitleExists(t), "channel_title must be restored after the up migration")
|
||||
require.NoError(t, m.Steps(1), "up 015 restores the shared transcripts shape")
|
||||
require.NoError(t, m.Steps(1), "up 016 recreates channel_caption_state (HEAD)")
|
||||
|
||||
migrateTo(t, m, head) // restore to HEAD for sibling tests
|
||||
}
|
||||
|
||||
// TestMigration012FixAutoSummarizeRLS proves 012 runs cleanly and flips any
|
||||
@@ -148,7 +155,11 @@ func TestMigration012FixAutoSummarizeRLS(t *testing.T) {
|
||||
|
||||
// Round-trip: down 012, then up 012 — must be idempotent.
|
||||
m := fileMigrator(t)
|
||||
require.NoError(t, m.Steps(-1), "down 012 must not error")
|
||||
require.NoError(t, m.Steps(1), "up 012 must re-apply cleanly")
|
||||
head := headVersion(t, m)
|
||||
|
||||
migrateTo(t, m, 11) // down 012 must not error
|
||||
migrateTo(t, m, 12) // up 012 must re-apply cleanly
|
||||
require.Equal(t, "true", autoSummarizeDefault(t), "default still TRUE after 012 re-applied")
|
||||
|
||||
migrateTo(t, m, head) // restore to HEAD for sibling tests
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
)
|
||||
|
||||
// seedVideo inserts a videos row whose id matches a summary's video_id, so the
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
|
||||
_ "github.com/jackc/pgx/v5/stdlib" // register the "pgx" database/sql driver for migrate
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/domain"
|
||||
"git.d-ma.be/mathias/tapir/internal/domain"
|
||||
)
|
||||
|
||||
//go:embed migrations/*.sql
|
||||
|
||||
@@ -11,9 +11,9 @@ import (
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/domain"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/ports"
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"git.d-ma.be/mathias/tapir/internal/domain"
|
||||
"git.d-ma.be/mathias/tapir/internal/ports"
|
||||
)
|
||||
|
||||
// Static check: Store satisfies the Sink port.
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
)
|
||||
|
||||
// seedBareVideo inserts a videos row with no summary, so the all-videos read and
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/domain"
|
||||
"git.d-ma.be/mathias/tapir/internal/domain"
|
||||
)
|
||||
|
||||
// GetTranscript returns the shared, stored transcript for a video keyed by the
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
)
|
||||
|
||||
func TestSetTranscriptStatus_RoundTrip(t *testing.T) {
|
||||
|
||||
@@ -6,9 +6,9 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/domain"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/ports"
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"git.d-ma.be/mathias/tapir/internal/domain"
|
||||
"git.d-ma.be/mathias/tapir/internal/ports"
|
||||
)
|
||||
|
||||
// Static check: Store satisfies the shared TranscriptStore port (ADR-021).
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/domain"
|
||||
"git.d-ma.be/mathias/tapir/internal/domain"
|
||||
)
|
||||
|
||||
// UpsertVideo persists a video's metadata and returns its durable store id (the
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/domain"
|
||||
"git.d-ma.be/mathias/tapir/internal/domain"
|
||||
)
|
||||
|
||||
func ytVideo(userID, provVideoID, title string) domain.Video {
|
||||
|
||||
@@ -18,8 +18,8 @@ import (
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/domain"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/metrics"
|
||||
"git.d-ma.be/mathias/tapir/internal/domain"
|
||||
"git.d-ma.be/mathias/tapir/internal/metrics"
|
||||
)
|
||||
|
||||
// Completer is the minimal LLM chat surface the Summarizer needs.
|
||||
|
||||
@@ -12,9 +12,9 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/domain"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/metrics"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/ports"
|
||||
"git.d-ma.be/mathias/tapir/internal/domain"
|
||||
"git.d-ma.be/mathias/tapir/internal/metrics"
|
||||
"git.d-ma.be/mathias/tapir/internal/ports"
|
||||
)
|
||||
|
||||
// TestSummarizerRecordsMetric verifies the summarizer→metrics wiring (ADR-030)
|
||||
|
||||
@@ -12,8 +12,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/domain"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/metrics"
|
||||
"git.d-ma.be/mathias/tapir/internal/domain"
|
||||
"git.d-ma.be/mathias/tapir/internal/metrics"
|
||||
)
|
||||
|
||||
// defaultPlayerBaseURL is the InnerTube / watch-page host. Overridable via
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/domain"
|
||||
"git.d-ma.be/mathias/tapir/internal/domain"
|
||||
)
|
||||
|
||||
func TestVideoByID(t *testing.T) {
|
||||
|
||||
@@ -27,8 +27,8 @@ import (
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/domain"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/ports"
|
||||
"git.d-ma.be/mathias/tapir/internal/domain"
|
||||
"git.d-ma.be/mathias/tapir/internal/ports"
|
||||
)
|
||||
|
||||
// defaultBaseURL is the YouTube Data API v3 root. Overridable via Config.BaseURL
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/domain"
|
||||
"git.d-ma.be/mathias/tapir/internal/domain"
|
||||
)
|
||||
|
||||
// --- fakes ------------------------------------------------------------------
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/auth"
|
||||
"git.d-ma.be/mathias/tapir/internal/auth"
|
||||
)
|
||||
|
||||
// fakeWriter is a TokenWriter capturing the persisted (ref, value).
|
||||
|
||||
@@ -7,7 +7,7 @@ package ports
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/domain"
|
||||
"git.d-ma.be/mathias/tapir/internal/domain"
|
||||
)
|
||||
|
||||
// VideoSource is a video platform Tapir watches (YouTube, Vimeo).
|
||||
|
||||
@@ -19,9 +19,9 @@ import (
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/domain"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/ports"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/usecase"
|
||||
"git.d-ma.be/mathias/tapir/internal/domain"
|
||||
"git.d-ma.be/mathias/tapir/internal/ports"
|
||||
"git.d-ma.be/mathias/tapir/internal/usecase"
|
||||
)
|
||||
|
||||
// passCandidate is a video that passed all pre-filters (seen/manual/backoff)
|
||||
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/domain"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/runner"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/usecase"
|
||||
"git.d-ma.be/mathias/tapir/internal/domain"
|
||||
"git.d-ma.be/mathias/tapir/internal/runner"
|
||||
"git.d-ma.be/mathias/tapir/internal/usecase"
|
||||
)
|
||||
|
||||
const testUser = "11111111-1111-1111-1111-111111111111"
|
||||
|
||||
@@ -14,8 +14,8 @@ import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/domain"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/ports"
|
||||
"git.d-ma.be/mathias/tapir/internal/domain"
|
||||
"git.d-ma.be/mathias/tapir/internal/ports"
|
||||
)
|
||||
|
||||
// ErrNotImplemented marks scaffold methods awaiting implementation.
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/domain"
|
||||
"git.d-ma.be/mathias/tapir/internal/domain"
|
||||
)
|
||||
|
||||
// These tests pin the ADR-021 read-stored-first behaviour at the engine core:
|
||||
|
||||
@@ -3,7 +3,7 @@ package web
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
)
|
||||
|
||||
// handleAccount renders the account page: the user's display name, the
|
||||
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/web"
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"git.d-ma.be/mathias/tapir/internal/web"
|
||||
)
|
||||
|
||||
// fakeSecrets is a SecretRemover that records the refs it was asked to delete, so
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/chat"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/chat"
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
)
|
||||
|
||||
// Chatter is the per-video chat backend (ADR-027). *chat.Service satisfies it;
|
||||
|
||||
@@ -12,10 +12,10 @@ import (
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/chat"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/domain"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/web"
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/chat"
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"git.d-ma.be/mathias/tapir/internal/domain"
|
||||
"git.d-ma.be/mathias/tapir/internal/web"
|
||||
)
|
||||
|
||||
// videoZ is a video id used by the isolation test for a DIFFERENT user's video.
|
||||
|
||||
@@ -12,8 +12,8 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/auth"
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"git.d-ma.be/mathias/tapir/internal/auth"
|
||||
)
|
||||
|
||||
// Connections is the narrow write port the connect flow depends on (Clean
|
||||
|
||||
@@ -11,9 +11,9 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/auth"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/web"
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"git.d-ma.be/mathias/tapir/internal/auth"
|
||||
"git.d-ma.be/mathias/tapir/internal/web"
|
||||
)
|
||||
|
||||
// fakeWriter is a TokenWriter capturing the persisted (ref, value).
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
|
||||
"github.com/a-h/templ"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/domain"
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"git.d-ma.be/mathias/tapir/internal/domain"
|
||||
)
|
||||
|
||||
// Store is the read/write surface the web handlers depend on — a narrow port over
|
||||
|
||||
@@ -16,9 +16,9 @@ import (
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/domain"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/web"
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"git.d-ma.be/mathias/tapir/internal/domain"
|
||||
"git.d-ma.be/mathias/tapir/internal/web"
|
||||
)
|
||||
|
||||
// dsn points at the in-process Postgres started in TestMain. Handler tests run
|
||||
|
||||
@@ -27,8 +27,8 @@ import (
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/metrics"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/web"
|
||||
"git.d-ma.be/mathias/tapir/internal/metrics"
|
||||
"git.d-ma.be/mathias/tapir/internal/web"
|
||||
)
|
||||
|
||||
// Config is the OIDC + session configuration. cmd/tapir maps these from
|
||||
|
||||
@@ -14,8 +14,8 @@ import (
|
||||
josev4 "github.com/go-jose/go-jose/v4"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/web"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/web/oidc"
|
||||
"git.d-ma.be/mathias/tapir/internal/web"
|
||||
"git.d-ma.be/mathias/tapir/internal/web/oidc"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/web"
|
||||
"git.d-ma.be/mathias/tapir/internal/web"
|
||||
)
|
||||
|
||||
// randToken returns a URL-safe 256-bit random string for session IDs, OIDC
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/domain"
|
||||
"git.d-ma.be/mathias/tapir/internal/domain"
|
||||
)
|
||||
|
||||
// fakeFetcher is a web.VideoFetcher returning a fixed video (or an error),
|
||||
|
||||
@@ -3,7 +3,7 @@ package web
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/domain"
|
||||
"git.d-ma.be/mathias/tapir/internal/domain"
|
||||
)
|
||||
|
||||
// Processor runs the core summarization use case for a single already-discovered
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/web"
|
||||
"git.d-ma.be/mathias/tapir/internal/web"
|
||||
)
|
||||
|
||||
// fakeProcessor records ProcessVideo calls. With block set it parks until the
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
)
|
||||
|
||||
func renderVideoCard(t *testing.T, r store.SummaryRow) string {
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
"github.com/a-h/templ"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
)
|
||||
|
||||
// youtubeIDRe matches a canonical 11-char YouTube video id (the provider's
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
)
|
||||
|
||||
// Layout is the shared HTML shell. HTMX drives the progressive interactions
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/web"
|
||||
"git.d-ma.be/mathias/tapir/internal/web"
|
||||
)
|
||||
|
||||
// fakeAuth is a configurable web.Auth for the landing-page tests: it reports a
|
||||
|
||||
@@ -12,8 +12,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/domain"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/usecase"
|
||||
"git.d-ma.be/mathias/tapir/internal/domain"
|
||||
"git.d-ma.be/mathias/tapir/internal/usecase"
|
||||
)
|
||||
|
||||
// --- fake adapters ---------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user