Documentation
¶
Overview ¶
Package mockapi serves a fixed positronick.com API fixture over net/http/httptest-compatible handlers, implementing the read contract (GET /api/souls, /api/souls/{slug}, /api/listings(?type=), /api/listings/{slug}, /api/research, /api/blog(?kind=), /api/blog/{slug}, /api/blog/{slug}.md) and the auth contract (device flow, /api/me, api-key/create) for the CLI's golden and e2e tests. The dataset is deliberately frozen: golden files pin command output byte-for-byte against it, so changing a fixture value is a contract-test change.
Index ¶
Constants ¶
const ( AdminAPIKey = "posi_admin_test" AdminToken = "admin-token" PlebToken = "pleb-token" )
Admin fixture credentials. AdminAPIKey/AdminToken authenticate as an admin; PlebToken authenticates as a regular user (403 on admin routes); anything else is 401.
const ( FeedReleaseID = "01FEEDSEED0000000000000001" FeedBlogID = "01FEEDSEED0000000000000002" )
Seeded feed source ids — stable so the feed list/sync/update golden + tests can address them. FeedReleaseID is a github_release feed that syncs cleanly; FeedBlogID is a paused rss feed.
const ( // DeviceCode is the device_code issued by /api/auth/device/code. DeviceCode = "mock-device-code-0001" // UserCode is the user_code the person types on the verification page. UserCode = "TJSPLLAV" // AccessToken is the session token granted once the device is approved; // it is the only bearer /api/me and api-key/create accept. AccessToken = "mock-session-token" // APIKey is the raw key minted by api-key/create, also accepted by // /api/me as x-api-key. APIKey = "posi_mockkey1234567890" )
Auth fixture values. The device flow is scripted: the first token poll for DeviceCode answers authorization_pending, every later poll succeeds with AccessToken. The advertised poll interval is 0 so tests never really wait — interval pacing (and slow_down growth) is unit-tested in internal/auth.
Variables ¶
var Listings = []api.Listing{ { ID: "01LSTCLAUDECODE000000000XX", Slug: "claude-code", ProfileHandle: "anthropic", ProfileName: "Anthropic", ProfileTier: ptr("official"), Name: "Claude Code", Type: "harness", Tagline: "Agentic coding in your terminal", Description: ptr("Anthropic's agentic coding harness."), Category: "AI/ML", Tags: []string{"agentic", "terminal"}, Official: true, SourceURL: "https://example.com/claude-code", RepoURL: ptr("https://example.com/anthropics/claude-code"), InstallCmd: ptr("npm install -g @anthropic-ai/claude-code"), Data: map[string]any{}, HasAsset: false, AssetVersion: nil, AssetContentHash: nil, Confidence: "official", Status: "published", DownloadCount: 120, ChargeCount: 9, CreatedAt: "2026-01-10T09:00:00.000Z", UpdatedAt: "2026-01-12T09:00:00.000Z", }, { ID: "01LSTGITHUBCLI0000000000XX", Slug: "github-cli", ProfileHandle: "github", ProfileName: "GitHub", ProfileTier: ptr("official"), Name: "GitHub CLI", Type: "cli", Tagline: "GitHub from the command line", Description: ptr("Work with issues, PRs and releases from the shell."), Category: "DevOps", Tags: []string{"git", "github"}, Official: true, SourceURL: "https://example.com/cli", RepoURL: ptr("https://example.com/cli/cli"), InstallCmd: ptr("brew install gh"), Data: map[string]any{}, HasAsset: false, AssetVersion: nil, AssetContentHash: nil, Confidence: "official", Status: "published", DownloadCount: 64, ChargeCount: 4, CreatedAt: "2026-02-01T09:00:00.000Z", UpdatedAt: "2026-02-03T09:00:00.000Z", }, { ID: "01LSTGRAFANAMCP000000000XX", Slug: "grafana-mcp", ProfileHandle: "grafana", ProfileName: "Grafana Labs", ProfileTier: ptr("official"), Name: "Grafana MCP", Type: "mcp", Tagline: "Dashboards, alerts and incidents as agent tools", Description: nil, Category: "DevOps", Tags: []string{"observability"}, Official: true, SourceURL: "https://example.com/grafana-mcp", RepoURL: nil, InstallCmd: nil, Data: map[string]any{}, HasAsset: false, AssetVersion: nil, AssetContentHash: nil, Confidence: "official", Status: "published", DownloadCount: 31, ChargeCount: 2, CreatedAt: "2026-03-15T09:00:00.000Z", UpdatedAt: "2026-03-15T09:00:00.000Z", }, { ID: "01LSTSUPERPOWERS00000000XX", Slug: "superpowers", ProfileHandle: "obra", ProfileName: "Jesse Vincent", ProfileTier: ptr("verified"), Name: "Superpowers", Type: "skill", Tagline: "A methodology pack that upgrades your coding agent", Description: ptr("Skills for planning, debugging and shipping."), Category: "AI/ML", Tags: []string{"skills", "methodology"}, Official: true, SourceURL: "https://example.com/superpowers", RepoURL: ptr("https://example.com/obra/superpowers"), InstallCmd: nil, Data: map[string]any{"bundles": []any{"pr-to-green"}}, HasAsset: true, AssetVersion: ptr("1.0.0"), AssetContentHash: ptr("11aa11aa11aa11aa11aa11aa11aa11aa11aa11aa11aa11aa11aa11aa11aa11aa"), Confidence: "official", Status: "published", DownloadCount: 18, ChargeCount: 6, CreatedAt: "2026-04-01T09:00:00.000Z", UpdatedAt: "2026-04-02T09:00:00.000Z", }, { ID: "01LSTPLAINSKILL000000000XX", Slug: "plain-skill", ProfileHandle: "obra", ProfileName: "Jesse Vincent", ProfileTier: ptr("verified"), Name: "Plain Skill", Type: "skill", Tagline: "A skill listing with no hosted SKILL.md asset", Description: ptr("Installs through its own command — no hosted asset for this listing."), Category: "AI/ML", Tags: []string{"skills"}, Official: true, SourceURL: "https://example.com/plain-skill", RepoURL: ptr("https://example.com/obra/plain-skill"), InstallCmd: ptr("npx plain-skill install"), Data: map[string]any{}, HasAsset: false, AssetVersion: nil, AssetContentHash: nil, Confidence: "official", Status: "published", DownloadCount: 3, ChargeCount: 0, CreatedAt: "2026-04-03T09:00:00.000Z", UpdatedAt: "2026-04-03T09:00:00.000Z", }, { ID: "01LSTMISMATCHSKILL0000000", Slug: "mismatched-skill", ProfileHandle: "obra", ProfileName: "Jesse Vincent", ProfileTier: ptr("verified"), Name: "Mismatched Skill", Type: "skill", Tagline: "A hosted skill whose SKILL.md name disagrees with its slug", Description: ptr("Fixture for the frontmatter-name-must-equal-slug install rule."), Category: "AI/ML", Tags: []string{"skills"}, Official: true, SourceURL: "https://example.com/mismatched-skill", RepoURL: ptr("https://example.com/obra/mismatched-skill"), InstallCmd: nil, Data: map[string]any{}, HasAsset: true, AssetVersion: ptr("1.0.0"), AssetContentHash: ptr("22bb22bb22bb22bb22bb22bb22bb22bb22bb22bb22bb22bb22bb22bb22bb22bb"), Confidence: "official", Status: "published", DownloadCount: 1, ChargeCount: 0, CreatedAt: "2026-04-04T09:00:00.000Z", UpdatedAt: "2026-04-04T09:00:00.000Z", }, { ID: "01LSTPRTOGREEN0000000000XX", Slug: "pr-to-green", ProfileHandle: "nsollazzo", ProfileName: "Nicholas Sollazzo", ProfileTier: ptr("official"), Name: "PR to Green", Type: "loop", Tagline: "Drive a pull request until CI is green and review approves", Description: ptr("A goal loop: fix findings, push, re-check, repeat."), Category: "Productivity", Tags: []string{"ci", "automation"}, Official: true, SourceURL: "https://example.com/pr-to-green", RepoURL: nil, InstallCmd: nil, Data: map[string]any{ "goal": "The PR has a fresh approval, green CI, and is mergeable", "checkCommand": "gh pr checks --json state", "exitCondition": "All checks pass and the latest review is APPROVED", "maxIterations": 20, "compatibleTools": []any{"claude-code", "codex"}, "kickoff": "Run the pr-to-green loop on the open PR:\n1. Read every review finding.\n2. Fix the valid ones, push back on the invalid.\n3. Push and re-check until green.", "bundles": []any{"superpowers"}, }, HasAsset: false, AssetVersion: nil, AssetContentHash: nil, Confidence: "official", Status: "published", DownloadCount: 12, ChargeCount: 3, CreatedAt: "2026-05-01T09:00:00.000Z", UpdatedAt: "2026-05-02T09:00:00.000Z", }, { ID: "01LSTDAILYBRIEFING000000XX", Slug: "daily-briefing", ProfileHandle: "nsollazzo", ProfileName: "Nicholas Sollazzo", ProfileTier: ptr("official"), Name: "Daily Briefing", Type: "bot", Tagline: "A weekday standup bot that summarizes overnight activity", Description: ptr("A scheduled briefing covering GitHub PRs, incidents, and the calendar."), Category: "Productivity", Tags: []string{"standup", "schedule"}, Official: true, SourceURL: "https://example.com/daily-briefing", RepoURL: nil, InstallCmd: nil, Data: map[string]any{ "prompt": "Summarize overnight PRs, incidents, and calendar for the team standup.", "integrations": []any{"github", "slack"}, "schedule": "0 9 * * 1-5", "platforms": []any{"hermes"}, "soulSlug": "sherlock", }, HasAsset: false, AssetVersion: nil, AssetContentHash: nil, Confidence: "official", Status: "published", DownloadCount: 8, ChargeCount: 1, CreatedAt: "2026-06-01T09:00:00.000Z", UpdatedAt: "2026-06-02T09:00:00.000Z", }, }
Listings is the fixture registry: six listings covering harness, cli, mcp, skill, a loop with a full LoopData payload (the `loop show` fixture), and a bot with a full BotData payload (the `bot install` fixture). The agent, plugin and memory types are deliberately empty so empty-result output is pinned too.
var MeUser = api.User{
ID: "usr_mock0001",
Name: "Ada Lovelace",
Email: "ada@example.com",
Image: ptr("https://example.com/ada.png"),
GithubLogin: ptr("ada"),
}
MeUser is the identity /api/me answers for valid credentials (isAdmin false).
var Posts = []api.Post{ { PostCard: api.PostCard{ ID: "01POSTINTRO0000000000000XX", Slug: "introducing-positronick", SlugHistory: []string{}, Kind: "article", Title: "Introducing Positronick", Excerpt: "Publish once, discover and use anywhere — the marketplace for everything AI.", Description: ptr("Why we built a provider- and framework-agnostic registry for souls, MCP servers, CLIs, agents, skills and more."), ContentHash: "1111aaaa1111aaaa1111aaaa1111aaaa1111aaaa1111aaaa1111aaaa1111aaaa", Version: "1.0.0", Category: "Announcements", Tags: []string{"launch", "registry"}, AuthorHandle: ptr("positronick"), AuthorName: ptr("Positronick"), AuthorAvatar: ptr("https://example.com/positronick.png"), AuthorTier: ptr("official"), ListingSlug: nil, ListingName: nil, CanonicalURL: nil, Status: "published", ViewCount: 128, PublishedAt: ptr("2026-05-20T12:00:00.000Z"), CreatedAt: "2026-05-20T12:00:00.000Z", UpdatedAt: "2026-05-21T09:00:00.000Z", }, Content: "# Introducing Positronick\n\nPublish once — discover and use anywhere.\n", }, { PostCard: api.PostCard{ ID: "01POSTCLIRELEASE000000000X", Slug: "positronick-cli-v0-1-0", SlugHistory: []string{}, Kind: "release", Title: "Positronick CLI v0.1.0", Excerpt: "Install souls and browse the registry from your terminal.", Description: nil, ContentHash: "2222bbbb2222bbbb2222bbbb2222bbbb2222bbbb2222bbbb2222bbbb2222bbbb", Version: "1.0.0", Category: "Releases", Tags: []string{"cli", "release"}, AuthorHandle: ptr("nsollazzo"), AuthorName: ptr("Nicholas Sollazzo"), AuthorAvatar: nil, AuthorTier: ptr("verified"), ListingSlug: ptr("positronick-cli"), ListingName: ptr("Positronick CLI"), CanonicalURL: ptr("https://github.com/positronick/cli/releases/tag/v0.1.0"), Status: "published", ViewCount: 42, PublishedAt: ptr("2026-06-01T09:00:00.000Z"), CreatedAt: "2026-06-01T09:00:00.000Z", UpdatedAt: "2026-06-01T09:00:00.000Z", }, Content: "# Positronick CLI v0.1.0\n\nThe first public release of the command-line client.\n", }, { PostCard: api.PostCard{ ID: "01POSTOPENCLAWLINK0000000X", Slug: "openclaw-joins-the-registry", SlugHistory: []string{}, Kind: "link", Title: "OpenClaw joins the registry", Excerpt: "A new open-source agent harness is now listed on Positronick.", Description: nil, ContentHash: "3333cccc3333cccc3333cccc3333cccc3333cccc3333cccc3333cccc3333cccc", Version: "1.0.0", Category: "Community", Tags: []string{"openclaw", "news"}, AuthorHandle: nil, AuthorName: nil, AuthorAvatar: nil, AuthorTier: nil, ListingSlug: nil, ListingName: nil, CanonicalURL: ptr("https://example.com/openclaw-joins"), Status: "published", ViewCount: 9, PublishedAt: ptr("2026-06-10T08:00:00.000Z"), CreatedAt: "2026-06-10T08:00:00.000Z", UpdatedAt: "2026-06-10T08:00:00.000Z", }, Content: "OpenClaw, an open-source agent harness, is now listed on Positronick.\n", }, }
Posts is the fixture blog: one post of each kind (article, release, link) with deliberately different publish dates, authors and nullable fields so --kind filtering, newest-first ordering, the did-you-mean hint and the null-field rendering each have something to disagree about. Ordered oldest-first; the handler sorts newest-first like the server. Content is the body only (no frontmatter) — the JSON detail's `content`; the .md endpoint wraps it in frontmatter via PostMarkdown.
var ResearchPosts = []api.ResearchItem{ { Slug: "shipping-the-cli", Title: "Shipping the Positronick CLI", Excerpt: "Install souls and browse the registry from your terminal.", Kind: "article", Category: "Engineering", Tags: []string{"cli", "launch"}, URL: "https://positronick.com/blog/shipping-the-cli", MdURL: "https://positronick.com/api/blog/shipping-the-cli.md", CanonicalURL: nil, ContentHash: "dddd4444dddd4444dddd4444dddd4444dddd4444dddd4444dddd4444dddd4444", PublishedAt: ptr("2026-05-20T12:00:00.000Z"), }, { Slug: "hermes-v2-1-0", Title: "Hermes v2.1.0", Excerpt: "Tool-calling fixes and a faster device flow.", Kind: "release", Category: "Releases", Tags: []string{"hermes"}, URL: "https://positronick.com/blog/hermes-v2-1-0", MdURL: "https://positronick.com/api/blog/hermes-v2-1-0.md", CanonicalURL: ptr("https://github.com/NousResearch/hermes/releases/tag/v2.1.0"), ContentHash: "eeee5555eeee5555eeee5555eeee5555eeee5555eeee5555eeee5555eeee5555", PublishedAt: ptr("2026-06-01T09:00:00.000Z"), }, { Slug: "openclaw-launch", Title: "OpenClaw launches its agent harness", Excerpt: "A new open-source harness joins the registry.", Kind: "link", Category: "Community", Tags: []string{"openclaw", "news"}, URL: "https://positronick.com/blog/openclaw-launch", MdURL: "https://positronick.com/api/blog/openclaw-launch.md", CanonicalURL: ptr("https://example.com/openclaw-launch"), ContentHash: "ffff6666ffff6666ffff6666ffff6666ffff6666ffff6666ffff6666ffff6666", PublishedAt: ptr("2026-06-10T08:00:00.000Z"), }, }
ResearchPosts is the fixture "what's new" feed: one of each post kind (article, release, link) with deliberately different publish dates, categories and tags so --since/--kind/--category/--tag and the `latest` high-water mark each have something to disagree about. Ordered oldest-first; the handler sorts newest-first like the server.
var SkillContent = map[string]string{
"superpowers": "---\nname: superpowers\ndescription: Skills for planning, debugging and shipping.\n" +
"---\n\n# Superpowers\n\nA battle-tested methodology pack: plan, then execute, then verify.\n",
"mismatched-skill": "---\nname: not-mismatched-skill\ndescription: name disagrees with the slug.\n" +
"---\n\n# Mismatched Skill\n",
}
SkillContent is the fixture hosted SKILL.md body for skill listings whose HasAsset is true, keyed by slug — kept separate from api.Listing since the real JSON detail endpoint has no content field (only the .md endpoint serves the body; see internal/api's SkillMarkdown). superpowers' frontmatter name matches its slug (the happy path); mismatched-skill's deliberately doesn't, for the name-must-equal-slug install rule.
var Souls = []api.Soul{ { SoulCard: api.SoulCard{ ID: "01SOULSHERLOCK0000000000XX", Slug: "sherlock", SlugHistory: []string{"holmes"}, Name: "Sherlock", AuthorHandle: "acdoyle", AuthorName: ptr("Arthur Conan Doyle"), AuthorURL: ptr("https://example.com/acdoyle"), Tagline: "Deductive debugging: reason from evidence, never from vibes", Description: ptr("A consulting-detective personality for root-cause analysis."), Category: "Technical", Tags: []string{"detective", "reasoning"}, Frameworks: []string{"hermes", "claude-code"}, Models: []string{"claude-fable-5"}, Version: "1.2.0", License: "MIT", RepoURL: ptr("https://example.com/souls/sherlock"), ContentHash: "aaaa1111aaaa1111aaaa1111aaaa1111aaaa1111aaaa1111aaaa1111aaaa1111", Status: "published", DownloadCount: 42, ChargeCount: 5, RatingAvg: ptr(4.5), RatingCount: 8, ArenaRank: ptr(2), CreatedAt: "2026-01-05T09:00:00.000Z", UpdatedAt: "2026-01-06T09:00:00.000Z", }, Content: "---\nname: Sherlock\n---\n\n# SOUL.md\n\nYou are Sherlock.\n\n- Reason from evidence.\n- Never guess.\n", }, { SoulCard: api.SoulCard{ ID: "01SOULWATSON000000000000XX", Slug: "watson", SlugHistory: []string{}, Name: "Watson", AuthorHandle: "acdoyle", AuthorName: nil, AuthorURL: nil, Tagline: "A steady pair-programming companion", Description: nil, Category: "Professional", Tags: []string{"assistant"}, Frameworks: []string{"hermes"}, Models: []string{}, Version: "0.3.1", License: "Apache-2.0", RepoURL: nil, ContentHash: "bbbb2222bbbb2222bbbb2222bbbb2222bbbb2222bbbb2222bbbb2222bbbb2222", Status: "published", DownloadCount: 7, ChargeCount: 0, RatingAvg: nil, RatingCount: 0, ArenaRank: nil, CreatedAt: "2026-02-10T09:00:00.000Z", UpdatedAt: "2026-02-10T09:00:00.000Z", }, Content: "# SOUL.md\n\nYou are Watson. You assist, summarize, and keep the record.\n", }, { SoulCard: api.SoulCard{ ID: "01SOULMORIARTY0000000000XX", Slug: "moriarty", SlugHistory: []string{}, Name: "Moriarty", AuthorHandle: "napoleon-of-crime", AuthorName: ptr("James Moriarty"), AuthorURL: nil, Tagline: "An adversarial red-team persona that attacks every assumption your plan quietly makes", Description: ptr("Breaks plans before production does."), Category: "Experimental", Tags: []string{"adversarial", "red-team"}, Frameworks: []string{"openclaw"}, Models: []string{"claude-fable-5", "gpt-6"}, Version: "2.0.0", License: "MIT", RepoURL: nil, ContentHash: "cccc3333cccc3333cccc3333cccc3333cccc3333cccc3333cccc3333cccc3333", Status: "published", DownloadCount: 99, ChargeCount: 11, RatingAvg: ptr(4.9), RatingCount: 21, ArenaRank: ptr(1), CreatedAt: "2026-03-01T09:00:00.000Z", UpdatedAt: "2026-03-02T09:00:00.000Z", }, Content: "# SOUL.md\n\nYou are Moriarty. Attack the plan.\n", }, }
Souls is the fixture gallery: three souls with deliberately different download counts, dates, categories and frameworks so ranking, sorting and filtering each have something to disagree about.
Functions ¶
func Handler ¶
Handler returns an http.Handler implementing the read API over the fixture data, including the server's JSON error envelope on 404 and on an unknown ?type=. Requests to /api/souls/{slug}.md and /api/skills/{slug}.md are answered with 418 — both .md endpoints bump an install counter, so any read command hitting them is a bug the consuming test must surface.
func InstallHandler ¶
InstallHandler returns Handler plus the install contract: GET /api/souls/{slug}.md answers a soul's markdown body verbatim, and GET /api/skills/{slug}.md answers a skill's hosted SKILL.md body from SkillContent — the endpoints that bump the server's download counter. Read-command tests must keep using Handler (which answers 418 on both .md paths so an accidental hit fails loudly); only install-path tests opt into this handler.
func PostMarkdown ¶ added in v0.2.0
PostMarkdown renders a fixture post as the raw markdown file the .md endpoint serves: a minimal frontmatter block plus the body. It is deliberately distinct from the JSON detail's body-only `content`, so a test can prove `blog show --raw` read the .md endpoint. Shared by the handler and the CLI raw-output test.
Types ¶
This section is empty.