chore(ax): normalise test naming and usage annotations
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
a6c15980a3
commit
dd59b177c6
81 changed files with 555 additions and 500 deletions
|
|
@ -45,7 +45,7 @@ agentci:
|
||||||
assert.Equal(t, "claude", agent.Runner)
|
assert.Equal(t, "claude", agent.Runner)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadAgents_Good_MultipleAgents(t *testing.T) {
|
func TestLoadAgents_Good_MultipleAgents_Good(t *testing.T) {
|
||||||
cfg := newTestConfig(t, `
|
cfg := newTestConfig(t, `
|
||||||
agentci:
|
agentci:
|
||||||
agents:
|
agents:
|
||||||
|
|
@ -66,7 +66,7 @@ agentci:
|
||||||
assert.Contains(t, agents, "local-codex")
|
assert.Contains(t, agents, "local-codex")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadAgents_Good_SkipsInactive(t *testing.T) {
|
func TestLoadAgents_Good_SkipsInactive_Good(t *testing.T) {
|
||||||
cfg := newTestConfig(t, `
|
cfg := newTestConfig(t, `
|
||||||
agentci:
|
agentci:
|
||||||
agents:
|
agents:
|
||||||
|
|
@ -101,7 +101,7 @@ agentci:
|
||||||
assert.Contains(t, active, "active-agent")
|
assert.Contains(t, active, "active-agent")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadAgents_Good_Defaults(t *testing.T) {
|
func TestLoadAgents_Good_Defaults_Good(t *testing.T) {
|
||||||
cfg := newTestConfig(t, `
|
cfg := newTestConfig(t, `
|
||||||
agentci:
|
agentci:
|
||||||
agents:
|
agents:
|
||||||
|
|
@ -119,14 +119,14 @@ agentci:
|
||||||
assert.Equal(t, "claude", agent.Runner)
|
assert.Equal(t, "claude", agent.Runner)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadAgents_Good_NoConfig(t *testing.T) {
|
func TestLoadAgents_Good_NoConfig_Good(t *testing.T) {
|
||||||
cfg := newTestConfig(t, "")
|
cfg := newTestConfig(t, "")
|
||||||
agents, err := LoadAgents(cfg)
|
agents, err := LoadAgents(cfg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Empty(t, agents)
|
assert.Empty(t, agents)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadAgents_Bad_MissingHost(t *testing.T) {
|
func TestLoadAgents_Bad_MissingHost_Good(t *testing.T) {
|
||||||
cfg := newTestConfig(t, `
|
cfg := newTestConfig(t, `
|
||||||
agentci:
|
agentci:
|
||||||
agents:
|
agents:
|
||||||
|
|
@ -139,7 +139,7 @@ agentci:
|
||||||
assert.Contains(t, err.Error(), "host is required")
|
assert.Contains(t, err.Error(), "host is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadAgents_Good_WithDualRun(t *testing.T) {
|
func TestLoadAgents_Good_WithDualRun_Good(t *testing.T) {
|
||||||
cfg := newTestConfig(t, `
|
cfg := newTestConfig(t, `
|
||||||
agentci:
|
agentci:
|
||||||
agents:
|
agents:
|
||||||
|
|
@ -176,7 +176,7 @@ agentci:
|
||||||
assert.Equal(t, "/etc/core/keys/clotho.pub", cc.SigningKeyPath)
|
assert.Equal(t, "/etc/core/keys/clotho.pub", cc.SigningKeyPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadClothoConfig_Good_Defaults(t *testing.T) {
|
func TestLoadClothoConfig_Good_Defaults_Good(t *testing.T) {
|
||||||
cfg := newTestConfig(t, "")
|
cfg := newTestConfig(t, "")
|
||||||
cc, err := LoadClothoConfig(cfg)
|
cc, err := LoadClothoConfig(cfg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -204,7 +204,7 @@ func TestSaveAgent_Good(t *testing.T) {
|
||||||
assert.Equal(t, "haiku", agents["new-agent"].Model)
|
assert.Equal(t, "haiku", agents["new-agent"].Model)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSaveAgent_Good_WithDualRun(t *testing.T) {
|
func TestSaveAgent_Good_WithDualRun_Good(t *testing.T) {
|
||||||
cfg := newTestConfig(t, "")
|
cfg := newTestConfig(t, "")
|
||||||
|
|
||||||
err := SaveAgent(cfg, "verified-agent", AgentConfig{
|
err := SaveAgent(cfg, "verified-agent", AgentConfig{
|
||||||
|
|
@ -222,7 +222,7 @@ func TestSaveAgent_Good_WithDualRun(t *testing.T) {
|
||||||
assert.True(t, agents["verified-agent"].DualRun)
|
assert.True(t, agents["verified-agent"].DualRun)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSaveAgent_Good_OmitsEmptyOptionals(t *testing.T) {
|
func TestSaveAgent_Good_OmitsEmptyOptionals_Good(t *testing.T) {
|
||||||
cfg := newTestConfig(t, "")
|
cfg := newTestConfig(t, "")
|
||||||
|
|
||||||
err := SaveAgent(cfg, "minimal", AgentConfig{
|
err := SaveAgent(cfg, "minimal", AgentConfig{
|
||||||
|
|
@ -256,7 +256,7 @@ agentci:
|
||||||
assert.Contains(t, agents, "to-keep")
|
assert.Contains(t, agents, "to-keep")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRemoveAgent_Bad_NotFound(t *testing.T) {
|
func TestRemoveAgent_Bad_NotFound_Good(t *testing.T) {
|
||||||
cfg := newTestConfig(t, `
|
cfg := newTestConfig(t, `
|
||||||
agentci:
|
agentci:
|
||||||
agents:
|
agents:
|
||||||
|
|
@ -269,7 +269,7 @@ agentci:
|
||||||
assert.Contains(t, err.Error(), "not found")
|
assert.Contains(t, err.Error(), "not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRemoveAgent_Bad_NoAgents(t *testing.T) {
|
func TestRemoveAgent_Bad_NoAgents_Good(t *testing.T) {
|
||||||
cfg := newTestConfig(t, "")
|
cfg := newTestConfig(t, "")
|
||||||
err := RemoveAgent(cfg, "anything")
|
err := RemoveAgent(cfg, "anything")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
|
|
@ -294,14 +294,14 @@ agentci:
|
||||||
assert.False(t, agents["agent-b"].Active)
|
assert.False(t, agents["agent-b"].Active)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestListAgents_Good_Empty(t *testing.T) {
|
func TestListAgents_Good_Empty_Good(t *testing.T) {
|
||||||
cfg := newTestConfig(t, "")
|
cfg := newTestConfig(t, "")
|
||||||
agents, err := ListAgents(cfg)
|
agents, err := ListAgents(cfg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Empty(t, agents)
|
assert.Empty(t, agents)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRoundTrip_Good_SaveThenLoad(t *testing.T) {
|
func TestRoundTrip_Good_SaveThenLoad_Good(t *testing.T) {
|
||||||
cfg := newTestConfig(t, "")
|
cfg := newTestConfig(t, "")
|
||||||
|
|
||||||
err := SaveAgent(cfg, "alpha", AgentConfig{
|
err := SaveAgent(cfg, "alpha", AgentConfig{
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ func TestBuildSyncRepoList_Good(t *testing.T) {
|
||||||
assert.Equal(t, filepath.Join(basePath, "core"), repos[0].localPath)
|
assert.Equal(t, filepath.Join(basePath, "core"), repos[0].localPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildSyncRepoList_Bad_PathTraversal(t *testing.T) {
|
func TestBuildSyncRepoList_Bad_PathTraversal_Good(t *testing.T) {
|
||||||
basePath := filepath.Join(t.TempDir(), "repos")
|
basePath := filepath.Join(t.TempDir(), "repos")
|
||||||
|
|
||||||
_, err := buildSyncRepoList(nil, []string{"../escape"}, basePath)
|
_, err := buildSyncRepoList(nil, []string{"../escape"}, basePath)
|
||||||
|
|
@ -28,7 +28,7 @@ func TestBuildSyncRepoList_Bad_PathTraversal(t *testing.T) {
|
||||||
assert.Contains(t, err.Error(), "invalid repo argument")
|
assert.Contains(t, err.Error(), "invalid repo argument")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildSyncRepoList_Good_OwnerRepo(t *testing.T) {
|
func TestBuildSyncRepoList_Good_OwnerRepo_Good(t *testing.T) {
|
||||||
basePath := filepath.Join(t.TempDir(), "repos")
|
basePath := filepath.Join(t.TempDir(), "repos")
|
||||||
|
|
||||||
repos, err := buildSyncRepoList(nil, []string{"Host-UK/core"}, basePath)
|
repos, err := buildSyncRepoList(nil, []string{"Host-UK/core"}, basePath)
|
||||||
|
|
@ -38,7 +38,7 @@ func TestBuildSyncRepoList_Good_OwnerRepo(t *testing.T) {
|
||||||
assert.Equal(t, filepath.Join(basePath, "core"), repos[0].localPath)
|
assert.Equal(t, filepath.Join(basePath, "core"), repos[0].localPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildSyncRepoList_Bad_PathTraversal_OwnerRepo(t *testing.T) {
|
func TestBuildSyncRepoList_Bad_PathTraversal_OwnerRepo_Good(t *testing.T) {
|
||||||
basePath := filepath.Join(t.TempDir(), "repos")
|
basePath := filepath.Join(t.TempDir(), "repos")
|
||||||
|
|
||||||
_, err := buildSyncRepoList(nil, []string{"host-uk/../escape"}, basePath)
|
_, err := buildSyncRepoList(nil, []string{"host-uk/../escape"}, basePath)
|
||||||
|
|
@ -46,7 +46,7 @@ func TestBuildSyncRepoList_Bad_PathTraversal_OwnerRepo(t *testing.T) {
|
||||||
assert.Contains(t, err.Error(), "invalid repo argument")
|
assert.Contains(t, err.Error(), "invalid repo argument")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildSyncRepoList_Bad_PathTraversal_OwnerRepoEncoded(t *testing.T) {
|
func TestBuildSyncRepoList_Bad_PathTraversal_OwnerRepoEncoded_Good(t *testing.T) {
|
||||||
basePath := filepath.Join(t.TempDir(), "repos")
|
basePath := filepath.Join(t.TempDir(), "repos")
|
||||||
|
|
||||||
_, err := buildSyncRepoList(nil, []string{"host-uk%2F..%2Fescape"}, basePath)
|
_, err := buildSyncRepoList(nil, []string{"host-uk%2F..%2Fescape"}, basePath)
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ func TestBuildRepoList_Good(t *testing.T) {
|
||||||
assert.Equal(t, filepath.Join(basePath, "core"), repos[0].localPath)
|
assert.Equal(t, filepath.Join(basePath, "core"), repos[0].localPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildRepoList_Bad_PathTraversal(t *testing.T) {
|
func TestBuildRepoList_Bad_PathTraversal_Good(t *testing.T) {
|
||||||
basePath := filepath.Join(t.TempDir(), "repos")
|
basePath := filepath.Join(t.TempDir(), "repos")
|
||||||
|
|
||||||
_, err := buildRepoList(nil, []string{"../escape"}, basePath)
|
_, err := buildRepoList(nil, []string{"../escape"}, basePath)
|
||||||
|
|
@ -28,7 +28,7 @@ func TestBuildRepoList_Bad_PathTraversal(t *testing.T) {
|
||||||
assert.Contains(t, err.Error(), "invalid repo argument")
|
assert.Contains(t, err.Error(), "invalid repo argument")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildRepoList_Good_OwnerRepo(t *testing.T) {
|
func TestBuildRepoList_Good_OwnerRepo_Good(t *testing.T) {
|
||||||
basePath := filepath.Join(t.TempDir(), "repos")
|
basePath := filepath.Join(t.TempDir(), "repos")
|
||||||
|
|
||||||
repos, err := buildRepoList(nil, []string{"Host-UK/core"}, basePath)
|
repos, err := buildRepoList(nil, []string{"Host-UK/core"}, basePath)
|
||||||
|
|
@ -38,7 +38,7 @@ func TestBuildRepoList_Good_OwnerRepo(t *testing.T) {
|
||||||
assert.Equal(t, filepath.Join(basePath, "core"), repos[0].localPath)
|
assert.Equal(t, filepath.Join(basePath, "core"), repos[0].localPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildRepoList_Bad_PathTraversal_OwnerRepo(t *testing.T) {
|
func TestBuildRepoList_Bad_PathTraversal_OwnerRepo_Good(t *testing.T) {
|
||||||
basePath := filepath.Join(t.TempDir(), "repos")
|
basePath := filepath.Join(t.TempDir(), "repos")
|
||||||
|
|
||||||
_, err := buildRepoList(nil, []string{"host-uk/../escape"}, basePath)
|
_, err := buildRepoList(nil, []string{"host-uk/../escape"}, basePath)
|
||||||
|
|
@ -46,7 +46,7 @@ func TestBuildRepoList_Bad_PathTraversal_OwnerRepo(t *testing.T) {
|
||||||
assert.Contains(t, err.Error(), "invalid repo argument")
|
assert.Contains(t, err.Error(), "invalid repo argument")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildRepoList_Bad_PathTraversal_OwnerRepoEncoded(t *testing.T) {
|
func TestBuildRepoList_Bad_PathTraversal_OwnerRepoEncoded_Good(t *testing.T) {
|
||||||
basePath := filepath.Join(t.TempDir(), "repos")
|
basePath := filepath.Join(t.TempDir(), "repos")
|
||||||
|
|
||||||
_, err := buildRepoList(nil, []string{"host-uk%2F..%2Fescape"}, basePath)
|
_, err := buildRepoList(nil, []string{"host-uk%2F..%2Fescape"}, basePath)
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ func sampleBTCTalkPage(count int) string {
|
||||||
return page.String()
|
return page.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBitcoinTalkCollector_Collect_Good_OnePage(t *testing.T) {
|
func TestBitcoinTalkCollector_Collect_Good_OnePage_Good(t *testing.T) {
|
||||||
// Serve a single page with 5 posts (< 20, so collection stops after one page).
|
// Serve a single page with 5 posts (< 20, so collection stops after one page).
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
|
|
@ -75,7 +75,7 @@ func TestBitcoinTalkCollector_Collect_Good_OnePage(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBitcoinTalkCollector_Collect_Good_PageLimit(t *testing.T) {
|
func TestBitcoinTalkCollector_Collect_Good_PageLimit_Good(t *testing.T) {
|
||||||
pageCount := 0
|
pageCount := 0
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
pageCount++
|
pageCount++
|
||||||
|
|
@ -103,7 +103,7 @@ func TestBitcoinTalkCollector_Collect_Good_PageLimit(t *testing.T) {
|
||||||
assert.Equal(t, 2, pageCount)
|
assert.Equal(t, 2, pageCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBitcoinTalkCollector_Collect_Good_CancelledContext(t *testing.T) {
|
func TestBitcoinTalkCollector_Collect_Good_CancelledContext_Good(t *testing.T) {
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
_, _ = w.Write([]byte(sampleBTCTalkPage(5)))
|
_, _ = w.Write([]byte(sampleBTCTalkPage(5)))
|
||||||
|
|
@ -127,7 +127,7 @@ func TestBitcoinTalkCollector_Collect_Good_CancelledContext(t *testing.T) {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBitcoinTalkCollector_Collect_Bad_ServerError(t *testing.T) {
|
func TestBitcoinTalkCollector_Collect_Bad_ServerError_Good(t *testing.T) {
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
}))
|
}))
|
||||||
|
|
@ -151,7 +151,7 @@ func TestBitcoinTalkCollector_Collect_Bad_ServerError(t *testing.T) {
|
||||||
assert.Equal(t, 1, result.Errors)
|
assert.Equal(t, 1, result.Errors)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBitcoinTalkCollector_Collect_Good_EmitsEvents(t *testing.T) {
|
func TestBitcoinTalkCollector_Collect_Good_EmitsEvents_Good(t *testing.T) {
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
_, _ = w.Write([]byte(sampleBTCTalkPage(2)))
|
_, _ = w.Write([]byte(sampleBTCTalkPage(2)))
|
||||||
|
|
@ -209,7 +209,7 @@ func TestFetchPage_Good(t *testing.T) {
|
||||||
assert.Len(t, posts, 3)
|
assert.Len(t, posts, 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFetchPage_Bad_StatusCode(t *testing.T) {
|
func TestFetchPage_Bad_StatusCode_Good(t *testing.T) {
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusForbidden)
|
w.WriteHeader(http.StatusForbidden)
|
||||||
}))
|
}))
|
||||||
|
|
@ -224,7 +224,7 @@ func TestFetchPage_Bad_StatusCode(t *testing.T) {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFetchPage_Bad_InvalidHTML(t *testing.T) {
|
func TestFetchPage_Bad_InvalidHTML_Good(t *testing.T) {
|
||||||
// html.Parse is very forgiving, so serve an empty page.
|
// html.Parse is very forgiving, so serve an empty page.
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,12 @@ func TestBitcoinTalkCollector_Name_Good(t *testing.T) {
|
||||||
assert.Equal(t, "bitcointalk:12345", b.Name())
|
assert.Equal(t, "bitcointalk:12345", b.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBitcoinTalkCollector_Name_Good_URL(t *testing.T) {
|
func TestBitcoinTalkCollector_Name_Good_URL_Good(t *testing.T) {
|
||||||
b := &BitcoinTalkCollector{URL: "https://bitcointalk.org/index.php?topic=12345.0"}
|
b := &BitcoinTalkCollector{URL: "https://bitcointalk.org/index.php?topic=12345.0"}
|
||||||
assert.Equal(t, "bitcointalk:url", b.Name())
|
assert.Equal(t, "bitcointalk:url", b.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBitcoinTalkCollector_Collect_Bad_NoTopicID(t *testing.T) {
|
func TestBitcoinTalkCollector_Collect_Bad_NoTopicID_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
|
|
||||||
|
|
@ -29,7 +29,7 @@ func TestBitcoinTalkCollector_Collect_Bad_NoTopicID(t *testing.T) {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBitcoinTalkCollector_Collect_Good_DryRun(t *testing.T) {
|
func TestBitcoinTalkCollector_Collect_Good_DryRun_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
cfg.DryRun = true
|
cfg.DryRun = true
|
||||||
|
|
@ -72,7 +72,7 @@ func TestParsePostsFromHTML_Good(t *testing.T) {
|
||||||
assert.Contains(t, posts[1].Content, "Running bitcoin!")
|
assert.Contains(t, posts[1].Content, "Running bitcoin!")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParsePostsFromHTML_Good_Empty(t *testing.T) {
|
func TestParsePostsFromHTML_Good_Empty_Good(t *testing.T) {
|
||||||
posts, err := ParsePostsFromHTML("<html><body></body></html>")
|
posts, err := ParsePostsFromHTML("<html><body></body></html>")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Empty(t, posts)
|
assert.Empty(t, posts)
|
||||||
|
|
@ -86,7 +86,7 @@ func TestFormatPostMarkdown_Good(t *testing.T) {
|
||||||
assert.Contains(t, md, "Hello, world!")
|
assert.Contains(t, md, "Hello, world!")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFormatPostMarkdown_Good_NoDate(t *testing.T) {
|
func TestFormatPostMarkdown_Good_NoDate_Good(t *testing.T) {
|
||||||
md := FormatPostMarkdown(5, "user", "", "Content here")
|
md := FormatPostMarkdown(5, "user", "", "Content here")
|
||||||
|
|
||||||
assert.Contains(t, md, "# Post 5 by user")
|
assert.Contains(t, md, "# Post 5 by user")
|
||||||
|
|
|
||||||
|
|
@ -56,13 +56,13 @@ func TestMergeResults_Good(t *testing.T) {
|
||||||
assert.Len(t, merged.Files, 3)
|
assert.Len(t, merged.Files, 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMergeResults_Good_NilResults(t *testing.T) {
|
func TestMergeResults_Good_NilResults_Good(t *testing.T) {
|
||||||
r1 := &Result{Items: 3}
|
r1 := &Result{Items: 3}
|
||||||
merged := MergeResults("test", r1, nil, nil)
|
merged := MergeResults("test", r1, nil, nil)
|
||||||
assert.Equal(t, 3, merged.Items)
|
assert.Equal(t, 3, merged.Items)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMergeResults_Good_Empty(t *testing.T) {
|
func TestMergeResults_Good_Empty_Good(t *testing.T) {
|
||||||
merged := MergeResults("empty")
|
merged := MergeResults("empty")
|
||||||
assert.Equal(t, 0, merged.Items)
|
assert.Equal(t, 0, merged.Items)
|
||||||
assert.Equal(t, 0, merged.Errors)
|
assert.Equal(t, 0, merged.Errors)
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import (
|
||||||
|
|
||||||
// --- GitHub collector: context cancellation and orchestration ---
|
// --- GitHub collector: context cancellation and orchestration ---
|
||||||
|
|
||||||
func TestGitHubCollector_Collect_Good_ContextCancelledInLoop(t *testing.T) {
|
func TestGitHubCollector_Collect_Good_ContextCancelledInLoop_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
cfg.DryRun = false
|
cfg.DryRun = false
|
||||||
|
|
@ -33,7 +33,7 @@ func TestGitHubCollector_Collect_Good_ContextCancelledInLoop(t *testing.T) {
|
||||||
assert.NotNil(t, result)
|
assert.NotNil(t, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGitHubCollector_Collect_Good_IssuesOnlyDryRunProgress(t *testing.T) {
|
func TestGitHubCollector_Collect_Good_IssuesOnlyDryRunProgress_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
cfg.DryRun = true
|
cfg.DryRun = true
|
||||||
|
|
@ -49,7 +49,7 @@ func TestGitHubCollector_Collect_Good_IssuesOnlyDryRunProgress(t *testing.T) {
|
||||||
assert.GreaterOrEqual(t, progressCount, 1)
|
assert.GreaterOrEqual(t, progressCount, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGitHubCollector_Collect_Good_PRsOnlyDryRunSkipsIssues(t *testing.T) {
|
func TestGitHubCollector_Collect_Good_PRsOnlyDryRunSkipsIssues_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
cfg.DryRun = true
|
cfg.DryRun = true
|
||||||
|
|
@ -61,7 +61,7 @@ func TestGitHubCollector_Collect_Good_PRsOnlyDryRunSkipsIssues(t *testing.T) {
|
||||||
assert.Equal(t, 0, result.Items)
|
assert.Equal(t, 0, result.Items)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGitHubCollector_Collect_Good_EmitsStartAndComplete(t *testing.T) {
|
func TestGitHubCollector_Collect_Good_EmitsStartAndComplete_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
cfg.DryRun = true
|
cfg.DryRun = true
|
||||||
|
|
@ -78,7 +78,7 @@ func TestGitHubCollector_Collect_Good_EmitsStartAndComplete(t *testing.T) {
|
||||||
assert.Equal(t, 1, completes)
|
assert.Equal(t, 1, completes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGitHubCollector_Collect_Good_NilDispatcherHandled(t *testing.T) {
|
func TestGitHubCollector_Collect_Good_NilDispatcherHandled_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
cfg.DryRun = true
|
cfg.DryRun = true
|
||||||
|
|
@ -91,7 +91,7 @@ func TestGitHubCollector_Collect_Good_NilDispatcherHandled(t *testing.T) {
|
||||||
assert.Equal(t, 0, result.Items)
|
assert.Equal(t, 0, result.Items)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFormatIssueMarkdown_Good_NoBodyNoURL(t *testing.T) {
|
func TestFormatIssueMarkdown_Good_NoBodyNoURL_Good(t *testing.T) {
|
||||||
issue := ghIssue{
|
issue := ghIssue{
|
||||||
Number: 1,
|
Number: 1,
|
||||||
Title: "No Body Issue",
|
Title: "No Body Issue",
|
||||||
|
|
@ -108,7 +108,7 @@ func TestFormatIssueMarkdown_Good_NoBodyNoURL(t *testing.T) {
|
||||||
|
|
||||||
// --- Market collector: fetchJSON edge cases ---
|
// --- Market collector: fetchJSON edge cases ---
|
||||||
|
|
||||||
func TestFetchJSON_Bad_NonJSONBody(t *testing.T) {
|
func TestFetchJSON_Bad_NonJSONBody_Good(t *testing.T) {
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
_, _ = w.Write([]byte(`<html>not json</html>`))
|
_, _ = w.Write([]byte(`<html>not json</html>`))
|
||||||
|
|
@ -119,17 +119,17 @@ func TestFetchJSON_Bad_NonJSONBody(t *testing.T) {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFetchJSON_Bad_MalformedURL(t *testing.T) {
|
func TestFetchJSON_Bad_MalformedURL_Good(t *testing.T) {
|
||||||
_, err := fetchJSON[coinData](context.Background(), "://bad-url")
|
_, err := fetchJSON[coinData](context.Background(), "://bad-url")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFetchJSON_Bad_ServerUnavailable(t *testing.T) {
|
func TestFetchJSON_Bad_ServerUnavailable_Good(t *testing.T) {
|
||||||
_, err := fetchJSON[coinData](context.Background(), "http://127.0.0.1:1")
|
_, err := fetchJSON[coinData](context.Background(), "http://127.0.0.1:1")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFetchJSON_Bad_Non200StatusCode(t *testing.T) {
|
func TestFetchJSON_Bad_Non200StatusCode_Good(t *testing.T) {
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
}))
|
}))
|
||||||
|
|
@ -140,7 +140,7 @@ func TestFetchJSON_Bad_Non200StatusCode(t *testing.T) {
|
||||||
assert.Contains(t, err.Error(), "unexpected status code")
|
assert.Contains(t, err.Error(), "unexpected status code")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMarketCollector_Collect_Bad_MissingCoinID(t *testing.T) {
|
func TestMarketCollector_Collect_Bad_MissingCoinID_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
|
|
||||||
|
|
@ -150,7 +150,7 @@ func TestMarketCollector_Collect_Bad_MissingCoinID(t *testing.T) {
|
||||||
assert.Contains(t, err.Error(), "coin ID is required")
|
assert.Contains(t, err.Error(), "coin ID is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMarketCollector_Collect_Good_NoDispatcher(t *testing.T) {
|
func TestMarketCollector_Collect_Good_NoDispatcher_Good(t *testing.T) {
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
data := coinData{ID: "test", Symbol: "tst", Name: "Test",
|
data := coinData{ID: "test", Symbol: "tst", Name: "Test",
|
||||||
|
|
@ -175,7 +175,7 @@ func TestMarketCollector_Collect_Good_NoDispatcher(t *testing.T) {
|
||||||
assert.Equal(t, 2, result.Items)
|
assert.Equal(t, 2, result.Items)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMarketCollector_Collect_Bad_CurrentFetchFails(t *testing.T) {
|
func TestMarketCollector_Collect_Bad_CurrentFetchFails_Good(t *testing.T) {
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
}))
|
}))
|
||||||
|
|
@ -197,7 +197,7 @@ func TestMarketCollector_Collect_Bad_CurrentFetchFails(t *testing.T) {
|
||||||
assert.Equal(t, 1, result.Errors)
|
assert.Equal(t, 1, result.Errors)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMarketCollector_CollectHistorical_Good_DefaultDays(t *testing.T) {
|
func TestMarketCollector_CollectHistorical_Good_DefaultDays_Good(t *testing.T) {
|
||||||
callCount := 0
|
callCount := 0
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
callCount++
|
callCount++
|
||||||
|
|
@ -229,7 +229,7 @@ func TestMarketCollector_CollectHistorical_Good_DefaultDays(t *testing.T) {
|
||||||
assert.Equal(t, 3, result.Items)
|
assert.Equal(t, 3, result.Items)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMarketCollector_CollectHistorical_Good_WithRateLimiter(t *testing.T) {
|
func TestMarketCollector_CollectHistorical_Good_WithRateLimiter_Good(t *testing.T) {
|
||||||
callCount := 0
|
callCount := 0
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
callCount++
|
callCount++
|
||||||
|
|
@ -263,7 +263,7 @@ func TestMarketCollector_CollectHistorical_Good_WithRateLimiter(t *testing.T) {
|
||||||
|
|
||||||
// --- State: error paths ---
|
// --- State: error paths ---
|
||||||
|
|
||||||
func TestState_Load_Bad_MalformedJSON(t *testing.T) {
|
func TestState_Load_Bad_MalformedJSON_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
m.Files["/state.json"] = `{invalid json`
|
m.Files["/state.json"] = `{invalid json`
|
||||||
|
|
||||||
|
|
@ -274,7 +274,7 @@ func TestState_Load_Bad_MalformedJSON(t *testing.T) {
|
||||||
|
|
||||||
// --- Process: additional coverage for uncovered branches ---
|
// --- Process: additional coverage for uncovered branches ---
|
||||||
|
|
||||||
func TestHTMLToMarkdown_Good_PreCodeBlock(t *testing.T) {
|
func TestHTMLToMarkdown_Good_PreCodeBlock_Good(t *testing.T) {
|
||||||
input := `<pre>some code here</pre>`
|
input := `<pre>some code here</pre>`
|
||||||
result, err := HTMLToMarkdown(input)
|
result, err := HTMLToMarkdown(input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -282,7 +282,7 @@ func TestHTMLToMarkdown_Good_PreCodeBlock(t *testing.T) {
|
||||||
assert.Contains(t, result, "some code here")
|
assert.Contains(t, result, "some code here")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHTMLToMarkdown_Good_StrongAndEmElements(t *testing.T) {
|
func TestHTMLToMarkdown_Good_StrongAndEmElements_Good(t *testing.T) {
|
||||||
input := `<strong>bold</strong> and <em>italic</em>`
|
input := `<strong>bold</strong> and <em>italic</em>`
|
||||||
result, err := HTMLToMarkdown(input)
|
result, err := HTMLToMarkdown(input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -290,21 +290,21 @@ func TestHTMLToMarkdown_Good_StrongAndEmElements(t *testing.T) {
|
||||||
assert.Contains(t, result, "*italic*")
|
assert.Contains(t, result, "*italic*")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHTMLToMarkdown_Good_InlineCode(t *testing.T) {
|
func TestHTMLToMarkdown_Good_InlineCode_Good(t *testing.T) {
|
||||||
input := `<code>var x = 1</code>`
|
input := `<code>var x = 1</code>`
|
||||||
result, err := HTMLToMarkdown(input)
|
result, err := HTMLToMarkdown(input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Contains(t, result, "`var x = 1`")
|
assert.Contains(t, result, "`var x = 1`")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHTMLToMarkdown_Good_AnchorWithHref(t *testing.T) {
|
func TestHTMLToMarkdown_Good_AnchorWithHref_Good(t *testing.T) {
|
||||||
input := `<a href="https://example.com">Click here</a>`
|
input := `<a href="https://example.com">Click here</a>`
|
||||||
result, err := HTMLToMarkdown(input)
|
result, err := HTMLToMarkdown(input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Contains(t, result, "[Click here](https://example.com)")
|
assert.Contains(t, result, "[Click here](https://example.com)")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHTMLToMarkdown_Good_ScriptTagRemoved(t *testing.T) {
|
func TestHTMLToMarkdown_Good_ScriptTagRemoved_Good(t *testing.T) {
|
||||||
input := `<html><body><script>alert('xss')</script><p>Safe text</p></body></html>`
|
input := `<html><body><script>alert('xss')</script><p>Safe text</p></body></html>`
|
||||||
result, err := HTMLToMarkdown(input)
|
result, err := HTMLToMarkdown(input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -312,7 +312,7 @@ func TestHTMLToMarkdown_Good_ScriptTagRemoved(t *testing.T) {
|
||||||
assert.NotContains(t, result, "alert")
|
assert.NotContains(t, result, "alert")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHTMLToMarkdown_Good_H1H2H3Headers(t *testing.T) {
|
func TestHTMLToMarkdown_Good_H1H2H3Headers_Good(t *testing.T) {
|
||||||
input := `<h1>One</h1><h2>Two</h2><h3>Three</h3>`
|
input := `<h1>One</h1><h2>Two</h2><h3>Three</h3>`
|
||||||
result, err := HTMLToMarkdown(input)
|
result, err := HTMLToMarkdown(input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -321,7 +321,7 @@ func TestHTMLToMarkdown_Good_H1H2H3Headers(t *testing.T) {
|
||||||
assert.Contains(t, result, "### Three")
|
assert.Contains(t, result, "### Three")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHTMLToMarkdown_Good_MultiParagraph(t *testing.T) {
|
func TestHTMLToMarkdown_Good_MultiParagraph_Good(t *testing.T) {
|
||||||
input := `<p>First paragraph</p><p>Second paragraph</p>`
|
input := `<p>First paragraph</p><p>Second paragraph</p>`
|
||||||
result, err := HTMLToMarkdown(input)
|
result, err := HTMLToMarkdown(input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -329,12 +329,12 @@ func TestHTMLToMarkdown_Good_MultiParagraph(t *testing.T) {
|
||||||
assert.Contains(t, result, "Second paragraph")
|
assert.Contains(t, result, "Second paragraph")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJSONToMarkdown_Bad_Malformed(t *testing.T) {
|
func TestJSONToMarkdown_Bad_Malformed_Good(t *testing.T) {
|
||||||
_, err := JSONToMarkdown(`{invalid}`)
|
_, err := JSONToMarkdown(`{invalid}`)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJSONToMarkdown_Good_FlatObject(t *testing.T) {
|
func TestJSONToMarkdown_Good_FlatObject_Good(t *testing.T) {
|
||||||
input := `{"name": "Alice", "age": 30}`
|
input := `{"name": "Alice", "age": 30}`
|
||||||
result, err := JSONToMarkdown(input)
|
result, err := JSONToMarkdown(input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -342,7 +342,7 @@ func TestJSONToMarkdown_Good_FlatObject(t *testing.T) {
|
||||||
assert.Contains(t, result, "**age:** 30")
|
assert.Contains(t, result, "**age:** 30")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJSONToMarkdown_Good_ScalarList(t *testing.T) {
|
func TestJSONToMarkdown_Good_ScalarList_Good(t *testing.T) {
|
||||||
input := `["hello", "world"]`
|
input := `["hello", "world"]`
|
||||||
result, err := JSONToMarkdown(input)
|
result, err := JSONToMarkdown(input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -350,14 +350,14 @@ func TestJSONToMarkdown_Good_ScalarList(t *testing.T) {
|
||||||
assert.Contains(t, result, "- world")
|
assert.Contains(t, result, "- world")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJSONToMarkdown_Good_ObjectContainingArray(t *testing.T) {
|
func TestJSONToMarkdown_Good_ObjectContainingArray_Good(t *testing.T) {
|
||||||
input := `{"items": [1, 2, 3]}`
|
input := `{"items": [1, 2, 3]}`
|
||||||
result, err := JSONToMarkdown(input)
|
result, err := JSONToMarkdown(input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Contains(t, result, "**items:**")
|
assert.Contains(t, result, "**items:**")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProcessor_Process_Bad_MissingDir(t *testing.T) {
|
func TestProcessor_Process_Bad_MissingDir_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
|
|
||||||
|
|
@ -367,7 +367,7 @@ func TestProcessor_Process_Bad_MissingDir(t *testing.T) {
|
||||||
assert.Contains(t, err.Error(), "directory is required")
|
assert.Contains(t, err.Error(), "directory is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProcessor_Process_Good_DryRunEmitsProgress(t *testing.T) {
|
func TestProcessor_Process_Good_DryRunEmitsProgress_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
cfg.DryRun = true
|
cfg.DryRun = true
|
||||||
|
|
@ -383,7 +383,7 @@ func TestProcessor_Process_Good_DryRunEmitsProgress(t *testing.T) {
|
||||||
assert.Equal(t, 1, progressCount)
|
assert.Equal(t, 1, progressCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProcessor_Process_Good_SkipsUnsupportedExtension(t *testing.T) {
|
func TestProcessor_Process_Good_SkipsUnsupportedExtension_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
m.Dirs["/input"] = true
|
m.Dirs["/input"] = true
|
||||||
m.Files["/input/data.csv"] = `a,b,c`
|
m.Files["/input/data.csv"] = `a,b,c`
|
||||||
|
|
@ -399,7 +399,7 @@ func TestProcessor_Process_Good_SkipsUnsupportedExtension(t *testing.T) {
|
||||||
assert.Equal(t, 1, result.Skipped)
|
assert.Equal(t, 1, result.Skipped)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProcessor_Process_Good_MarkdownPassthroughTrimmed(t *testing.T) {
|
func TestProcessor_Process_Good_MarkdownPassthroughTrimmed_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
m.Dirs["/input"] = true
|
m.Dirs["/input"] = true
|
||||||
m.Files["/input/readme.md"] = `# Hello World `
|
m.Files["/input/readme.md"] = `# Hello World `
|
||||||
|
|
@ -418,7 +418,7 @@ func TestProcessor_Process_Good_MarkdownPassthroughTrimmed(t *testing.T) {
|
||||||
assert.Equal(t, "# Hello World", content)
|
assert.Equal(t, "# Hello World", content)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProcessor_Process_Good_HTMExtensionHandled(t *testing.T) {
|
func TestProcessor_Process_Good_HTMExtensionHandled_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
m.Dirs["/input"] = true
|
m.Dirs["/input"] = true
|
||||||
m.Files["/input/page.htm"] = `<h1>HTM File</h1>`
|
m.Files["/input/page.htm"] = `<h1>HTM File</h1>`
|
||||||
|
|
@ -433,7 +433,7 @@ func TestProcessor_Process_Good_HTMExtensionHandled(t *testing.T) {
|
||||||
assert.Equal(t, 1, result.Items)
|
assert.Equal(t, 1, result.Items)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProcessor_Process_Good_NilDispatcherHandled(t *testing.T) {
|
func TestProcessor_Process_Good_NilDispatcherHandled_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
m.Dirs["/input"] = true
|
m.Dirs["/input"] = true
|
||||||
m.Files["/input/test.html"] = `<p>Text</p>`
|
m.Files["/input/test.html"] = `<p>Text</p>`
|
||||||
|
|
@ -451,12 +451,12 @@ func TestProcessor_Process_Good_NilDispatcherHandled(t *testing.T) {
|
||||||
|
|
||||||
// --- BitcoinTalk: additional edge cases ---
|
// --- BitcoinTalk: additional edge cases ---
|
||||||
|
|
||||||
func TestBitcoinTalkCollector_Name_Good_EmptyTopicAndURL(t *testing.T) {
|
func TestBitcoinTalkCollector_Name_Good_EmptyTopicAndURL_Good(t *testing.T) {
|
||||||
b := &BitcoinTalkCollector{}
|
b := &BitcoinTalkCollector{}
|
||||||
assert.Equal(t, "bitcointalk:", b.Name())
|
assert.Equal(t, "bitcointalk:", b.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBitcoinTalkCollector_Collect_Good_NilDispatcherHandled(t *testing.T) {
|
func TestBitcoinTalkCollector_Collect_Good_NilDispatcherHandled_Good(t *testing.T) {
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
_, _ = w.Write([]byte(sampleBTCTalkPage(2)))
|
_, _ = w.Write([]byte(sampleBTCTalkPage(2)))
|
||||||
|
|
@ -480,7 +480,7 @@ func TestBitcoinTalkCollector_Collect_Good_NilDispatcherHandled(t *testing.T) {
|
||||||
assert.Equal(t, 2, result.Items)
|
assert.Equal(t, 2, result.Items)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBitcoinTalkCollector_Collect_Good_DryRunEmitsProgress(t *testing.T) {
|
func TestBitcoinTalkCollector_Collect_Good_DryRunEmitsProgress_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
cfg.DryRun = true
|
cfg.DryRun = true
|
||||||
|
|
@ -496,7 +496,7 @@ func TestBitcoinTalkCollector_Collect_Good_DryRunEmitsProgress(t *testing.T) {
|
||||||
assert.True(t, progressEmitted)
|
assert.True(t, progressEmitted)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParsePostsFromHTML_Good_PostWithNoInnerContent(t *testing.T) {
|
func TestParsePostsFromHTML_Good_PostWithNoInnerContent_Good(t *testing.T) {
|
||||||
htmlContent := `<html><body>
|
htmlContent := `<html><body>
|
||||||
<div class="post">
|
<div class="post">
|
||||||
<div class="poster_info">user1</div>
|
<div class="poster_info">user1</div>
|
||||||
|
|
@ -507,7 +507,7 @@ func TestParsePostsFromHTML_Good_PostWithNoInnerContent(t *testing.T) {
|
||||||
assert.Empty(t, posts)
|
assert.Empty(t, posts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFormatPostMarkdown_Good_WithDateContent(t *testing.T) {
|
func TestFormatPostMarkdown_Good_WithDateContent_Good(t *testing.T) {
|
||||||
md := FormatPostMarkdown(1, "alice", "2025-01-15", "Hello world")
|
md := FormatPostMarkdown(1, "alice", "2025-01-15", "Hello world")
|
||||||
assert.Contains(t, md, "# Post 1 by alice")
|
assert.Contains(t, md, "# Post 1 by alice")
|
||||||
assert.Contains(t, md, "**Date:** 2025-01-15")
|
assert.Contains(t, md, "**Date:** 2025-01-15")
|
||||||
|
|
@ -516,7 +516,7 @@ func TestFormatPostMarkdown_Good_WithDateContent(t *testing.T) {
|
||||||
|
|
||||||
// --- Papers collector: edge cases ---
|
// --- Papers collector: edge cases ---
|
||||||
|
|
||||||
func TestPapersCollector_Collect_Good_DryRunEmitsProgress(t *testing.T) {
|
func TestPapersCollector_Collect_Good_DryRunEmitsProgress_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
cfg.DryRun = true
|
cfg.DryRun = true
|
||||||
|
|
@ -532,7 +532,7 @@ func TestPapersCollector_Collect_Good_DryRunEmitsProgress(t *testing.T) {
|
||||||
assert.True(t, progressEmitted)
|
assert.True(t, progressEmitted)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPapersCollector_Collect_Good_NilDispatcherIACR(t *testing.T) {
|
func TestPapersCollector_Collect_Good_NilDispatcherIACR_Good(t *testing.T) {
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
_, _ = w.Write([]byte(sampleIACRHTML))
|
_, _ = w.Write([]byte(sampleIACRHTML))
|
||||||
|
|
@ -556,7 +556,7 @@ func TestPapersCollector_Collect_Good_NilDispatcherIACR(t *testing.T) {
|
||||||
assert.Equal(t, 2, result.Items)
|
assert.Equal(t, 2, result.Items)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestArXivEntryToPaper_Good_NoAlternateLink(t *testing.T) {
|
func TestArXivEntryToPaper_Good_NoAlternateLink_Good(t *testing.T) {
|
||||||
entry := arxivEntry{
|
entry := arxivEntry{
|
||||||
ID: "http://arxiv.org/abs/2501.99999v1",
|
ID: "http://arxiv.org/abs/2501.99999v1",
|
||||||
Title: "No Alternate",
|
Title: "No Alternate",
|
||||||
|
|
@ -571,7 +571,7 @@ func TestArXivEntryToPaper_Good_NoAlternateLink(t *testing.T) {
|
||||||
|
|
||||||
// --- Excavator: additional edge cases ---
|
// --- Excavator: additional edge cases ---
|
||||||
|
|
||||||
func TestExcavator_Run_Good_ResumeLoadError(t *testing.T) {
|
func TestExcavator_Run_Good_ResumeLoadError_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
m.Files["/output/.collect-state.json"] = `{invalid`
|
m.Files["/output/.collect-state.json"] = `{invalid`
|
||||||
|
|
||||||
|
|
@ -591,7 +591,7 @@ func TestExcavator_Run_Good_ResumeLoadError(t *testing.T) {
|
||||||
|
|
||||||
// --- RateLimiter: additional edge cases ---
|
// --- RateLimiter: additional edge cases ---
|
||||||
|
|
||||||
func TestRateLimiter_Wait_Good_QuickSuccessiveCallsAfterDelay(t *testing.T) {
|
func TestRateLimiter_Wait_Good_QuickSuccessiveCallsAfterDelay_Good(t *testing.T) {
|
||||||
rl := NewRateLimiter()
|
rl := NewRateLimiter()
|
||||||
rl.SetDelay("fast", 1*time.Millisecond)
|
rl.SetDelay("fast", 1*time.Millisecond)
|
||||||
|
|
||||||
|
|
@ -610,7 +610,7 @@ func TestRateLimiter_Wait_Good_QuickSuccessiveCallsAfterDelay(t *testing.T) {
|
||||||
|
|
||||||
// --- FormatMarketSummary: with empty market data values ---
|
// --- FormatMarketSummary: with empty market data values ---
|
||||||
|
|
||||||
func TestFormatMarketSummary_Good_ZeroRank(t *testing.T) {
|
func TestFormatMarketSummary_Good_ZeroRank_Good(t *testing.T) {
|
||||||
data := &coinData{
|
data := &coinData{
|
||||||
Name: "Tiny Token",
|
Name: "Tiny Token",
|
||||||
Symbol: "tiny",
|
Symbol: "tiny",
|
||||||
|
|
@ -624,7 +624,7 @@ func TestFormatMarketSummary_Good_ZeroRank(t *testing.T) {
|
||||||
assert.NotContains(t, summary, "Market Cap Rank")
|
assert.NotContains(t, summary, "Market Cap Rank")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFormatMarketSummary_Good_ZeroSupply(t *testing.T) {
|
func TestFormatMarketSummary_Good_ZeroSupply_Good(t *testing.T) {
|
||||||
data := &coinData{
|
data := &coinData{
|
||||||
Name: "Zero Supply",
|
Name: "Zero Supply",
|
||||||
Symbol: "zs",
|
Symbol: "zs",
|
||||||
|
|
@ -638,7 +638,7 @@ func TestFormatMarketSummary_Good_ZeroSupply(t *testing.T) {
|
||||||
assert.NotContains(t, summary, "Total Supply")
|
assert.NotContains(t, summary, "Total Supply")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFormatMarketSummary_Good_NoLastUpdated(t *testing.T) {
|
func TestFormatMarketSummary_Good_NoLastUpdated_Good(t *testing.T) {
|
||||||
data := &coinData{
|
data := &coinData{
|
||||||
Name: "No Update",
|
Name: "No Update",
|
||||||
Symbol: "nu",
|
Symbol: "nu",
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ type errorLimiterWaiter struct{}
|
||||||
|
|
||||||
// --- Processor: list error ---
|
// --- Processor: list error ---
|
||||||
|
|
||||||
func TestProcessor_Process_Bad_ListError(t *testing.T) {
|
func TestProcessor_Process_Bad_ListError_Good(t *testing.T) {
|
||||||
em := &errorMedium{MockMedium: io.NewMockMedium(), listErr: testErr("list denied")}
|
em := &errorMedium{MockMedium: io.NewMockMedium(), listErr: testErr("list denied")}
|
||||||
cfg := &Config{Output: em, OutputDir: "/output", Dispatcher: NewDispatcher()}
|
cfg := &Config{Output: em, OutputDir: "/output", Dispatcher: NewDispatcher()}
|
||||||
|
|
||||||
|
|
@ -98,7 +98,7 @@ func TestProcessor_Process_Bad_ListError(t *testing.T) {
|
||||||
|
|
||||||
// --- Processor: ensureDir error ---
|
// --- Processor: ensureDir error ---
|
||||||
|
|
||||||
func TestProcessor_Process_Bad_EnsureDirError(t *testing.T) {
|
func TestProcessor_Process_Bad_EnsureDirError_Good(t *testing.T) {
|
||||||
em := &errorMedium{MockMedium: io.NewMockMedium(), ensureDirErr: testErr("mkdir denied")}
|
em := &errorMedium{MockMedium: io.NewMockMedium(), ensureDirErr: testErr("mkdir denied")}
|
||||||
// Need to ensure List returns entries
|
// Need to ensure List returns entries
|
||||||
em.MockMedium.Dirs["/input"] = true
|
em.MockMedium.Dirs["/input"] = true
|
||||||
|
|
@ -114,7 +114,7 @@ func TestProcessor_Process_Bad_EnsureDirError(t *testing.T) {
|
||||||
|
|
||||||
// --- Processor: context cancellation during processing ---
|
// --- Processor: context cancellation during processing ---
|
||||||
|
|
||||||
func TestProcessor_Process_Bad_ContextCancelledDuringLoop(t *testing.T) {
|
func TestProcessor_Process_Bad_ContextCancelledDuringLoop_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
m.Dirs["/input"] = true
|
m.Dirs["/input"] = true
|
||||||
m.Files["/input/a.html"] = "<h1>Test</h1>"
|
m.Files["/input/a.html"] = "<h1>Test</h1>"
|
||||||
|
|
@ -133,7 +133,7 @@ func TestProcessor_Process_Bad_ContextCancelledDuringLoop(t *testing.T) {
|
||||||
|
|
||||||
// --- Processor: read error during file processing ---
|
// --- Processor: read error during file processing ---
|
||||||
|
|
||||||
func TestProcessor_Process_Bad_ReadError(t *testing.T) {
|
func TestProcessor_Process_Bad_ReadError_Good(t *testing.T) {
|
||||||
em := &errorMedium{MockMedium: io.NewMockMedium(), readErr: testErr("read denied")}
|
em := &errorMedium{MockMedium: io.NewMockMedium(), readErr: testErr("read denied")}
|
||||||
em.MockMedium.Dirs["/input"] = true
|
em.MockMedium.Dirs["/input"] = true
|
||||||
em.MockMedium.Files["/input/test.html"] = "<h1>Test</h1>"
|
em.MockMedium.Files["/input/test.html"] = "<h1>Test</h1>"
|
||||||
|
|
@ -148,7 +148,7 @@ func TestProcessor_Process_Bad_ReadError(t *testing.T) {
|
||||||
|
|
||||||
// --- Processor: JSON conversion error ---
|
// --- Processor: JSON conversion error ---
|
||||||
|
|
||||||
func TestProcessor_Process_Bad_InvalidJSONFile(t *testing.T) {
|
func TestProcessor_Process_Bad_InvalidJSONFile_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
m.Dirs["/input"] = true
|
m.Dirs["/input"] = true
|
||||||
m.Files["/input/bad.json"] = "not valid json {"
|
m.Files["/input/bad.json"] = "not valid json {"
|
||||||
|
|
@ -166,7 +166,7 @@ func TestProcessor_Process_Bad_InvalidJSONFile(t *testing.T) {
|
||||||
|
|
||||||
// --- Processor: write error during output ---
|
// --- Processor: write error during output ---
|
||||||
|
|
||||||
func TestProcessor_Process_Bad_WriteError(t *testing.T) {
|
func TestProcessor_Process_Bad_WriteError_Good(t *testing.T) {
|
||||||
em := &errorMedium{MockMedium: io.NewMockMedium(), writeErr: testErr("disk full")}
|
em := &errorMedium{MockMedium: io.NewMockMedium(), writeErr: testErr("disk full")}
|
||||||
em.MockMedium.Dirs["/input"] = true
|
em.MockMedium.Dirs["/input"] = true
|
||||||
em.MockMedium.Files["/input/page.html"] = "<h1>Title</h1>"
|
em.MockMedium.Files["/input/page.html"] = "<h1>Title</h1>"
|
||||||
|
|
@ -181,7 +181,7 @@ func TestProcessor_Process_Bad_WriteError(t *testing.T) {
|
||||||
|
|
||||||
// --- Processor: successful processing with events ---
|
// --- Processor: successful processing with events ---
|
||||||
|
|
||||||
func TestProcessor_Process_Good_EmitsItemAndComplete(t *testing.T) {
|
func TestProcessor_Process_Good_EmitsItemAndComplete_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
m.Dirs["/input"] = true
|
m.Dirs["/input"] = true
|
||||||
m.Files["/input/page.html"] = "<h1>Title</h1><p>Body</p>"
|
m.Files["/input/page.html"] = "<h1>Title</h1><p>Body</p>"
|
||||||
|
|
@ -201,7 +201,7 @@ func TestProcessor_Process_Good_EmitsItemAndComplete(t *testing.T) {
|
||||||
|
|
||||||
// --- Papers: with rate limiter that fails ---
|
// --- Papers: with rate limiter that fails ---
|
||||||
|
|
||||||
func TestPapersCollector_CollectIACR_Bad_LimiterError(t *testing.T) {
|
func TestPapersCollector_CollectIACR_Bad_LimiterError_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
cfg.Limiter = NewRateLimiter()
|
cfg.Limiter = NewRateLimiter()
|
||||||
|
|
@ -215,7 +215,7 @@ func TestPapersCollector_CollectIACR_Bad_LimiterError(t *testing.T) {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPapersCollector_CollectArXiv_Bad_LimiterError(t *testing.T) {
|
func TestPapersCollector_CollectArXiv_Bad_LimiterError_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
cfg.Limiter = NewRateLimiter()
|
cfg.Limiter = NewRateLimiter()
|
||||||
|
|
@ -231,7 +231,7 @@ func TestPapersCollector_CollectArXiv_Bad_LimiterError(t *testing.T) {
|
||||||
|
|
||||||
// --- Papers: IACR with bad HTML response ---
|
// --- Papers: IACR with bad HTML response ---
|
||||||
|
|
||||||
func TestPapersCollector_CollectIACR_Bad_InvalidHTML(t *testing.T) {
|
func TestPapersCollector_CollectIACR_Bad_InvalidHTML_Good(t *testing.T) {
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
// Serve valid-ish HTML but with no papers - the parse succeeds but returns empty.
|
// Serve valid-ish HTML but with no papers - the parse succeeds but returns empty.
|
||||||
|
|
@ -256,7 +256,7 @@ func TestPapersCollector_CollectIACR_Bad_InvalidHTML(t *testing.T) {
|
||||||
|
|
||||||
// --- Papers: IACR write error ---
|
// --- Papers: IACR write error ---
|
||||||
|
|
||||||
func TestPapersCollector_CollectIACR_Bad_WriteError(t *testing.T) {
|
func TestPapersCollector_CollectIACR_Bad_WriteError_Good(t *testing.T) {
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
_, _ = w.Write([]byte(sampleIACRHTML))
|
_, _ = w.Write([]byte(sampleIACRHTML))
|
||||||
|
|
@ -280,7 +280,7 @@ func TestPapersCollector_CollectIACR_Bad_WriteError(t *testing.T) {
|
||||||
|
|
||||||
// --- Papers: IACR EnsureDir error ---
|
// --- Papers: IACR EnsureDir error ---
|
||||||
|
|
||||||
func TestPapersCollector_CollectIACR_Bad_EnsureDirError(t *testing.T) {
|
func TestPapersCollector_CollectIACR_Bad_EnsureDirError_Good(t *testing.T) {
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
_, _ = w.Write([]byte(sampleIACRHTML))
|
_, _ = w.Write([]byte(sampleIACRHTML))
|
||||||
|
|
@ -304,7 +304,7 @@ func TestPapersCollector_CollectIACR_Bad_EnsureDirError(t *testing.T) {
|
||||||
|
|
||||||
// --- Papers: arXiv write error ---
|
// --- Papers: arXiv write error ---
|
||||||
|
|
||||||
func TestPapersCollector_CollectArXiv_Bad_WriteError(t *testing.T) {
|
func TestPapersCollector_CollectArXiv_Bad_WriteError_Good(t *testing.T) {
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/xml")
|
w.Header().Set("Content-Type", "application/xml")
|
||||||
_, _ = w.Write([]byte(sampleArXivXML))
|
_, _ = w.Write([]byte(sampleArXivXML))
|
||||||
|
|
@ -328,7 +328,7 @@ func TestPapersCollector_CollectArXiv_Bad_WriteError(t *testing.T) {
|
||||||
|
|
||||||
// --- Papers: arXiv EnsureDir error ---
|
// --- Papers: arXiv EnsureDir error ---
|
||||||
|
|
||||||
func TestPapersCollector_CollectArXiv_Bad_EnsureDirError(t *testing.T) {
|
func TestPapersCollector_CollectArXiv_Bad_EnsureDirError_Good(t *testing.T) {
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/xml")
|
w.Header().Set("Content-Type", "application/xml")
|
||||||
_, _ = w.Write([]byte(sampleArXivXML))
|
_, _ = w.Write([]byte(sampleArXivXML))
|
||||||
|
|
@ -352,7 +352,7 @@ func TestPapersCollector_CollectArXiv_Bad_EnsureDirError(t *testing.T) {
|
||||||
|
|
||||||
// --- Papers: collectAll with dispatcher events ---
|
// --- Papers: collectAll with dispatcher events ---
|
||||||
|
|
||||||
func TestPapersCollector_CollectAll_Good_WithDispatcher(t *testing.T) {
|
func TestPapersCollector_CollectAll_Good_WithDispatcher_Good(t *testing.T) {
|
||||||
callCount := 0
|
callCount := 0
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
callCount++
|
callCount++
|
||||||
|
|
@ -387,7 +387,7 @@ func TestPapersCollector_CollectAll_Good_WithDispatcher(t *testing.T) {
|
||||||
|
|
||||||
// --- Papers: IACR with events on item emit ---
|
// --- Papers: IACR with events on item emit ---
|
||||||
|
|
||||||
func TestPapersCollector_CollectIACR_Good_EmitsItemEvents(t *testing.T) {
|
func TestPapersCollector_CollectIACR_Good_EmitsItemEvents_Good(t *testing.T) {
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
_, _ = w.Write([]byte(sampleIACRHTML))
|
_, _ = w.Write([]byte(sampleIACRHTML))
|
||||||
|
|
@ -415,7 +415,7 @@ func TestPapersCollector_CollectIACR_Good_EmitsItemEvents(t *testing.T) {
|
||||||
|
|
||||||
// --- Papers: arXiv with events on item emit ---
|
// --- Papers: arXiv with events on item emit ---
|
||||||
|
|
||||||
func TestPapersCollector_CollectArXiv_Good_EmitsItemEvents(t *testing.T) {
|
func TestPapersCollector_CollectArXiv_Good_EmitsItemEvents_Good(t *testing.T) {
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/xml")
|
w.Header().Set("Content-Type", "application/xml")
|
||||||
_, _ = w.Write([]byte(sampleArXivXML))
|
_, _ = w.Write([]byte(sampleArXivXML))
|
||||||
|
|
@ -443,7 +443,7 @@ func TestPapersCollector_CollectArXiv_Good_EmitsItemEvents(t *testing.T) {
|
||||||
|
|
||||||
// --- Market: collectCurrent write error (summary path) ---
|
// --- Market: collectCurrent write error (summary path) ---
|
||||||
|
|
||||||
func TestMarketCollector_Collect_Bad_WriteError(t *testing.T) {
|
func TestMarketCollector_Collect_Bad_WriteError_Good(t *testing.T) {
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
if strings.Contains(r.URL.Path, "/market_chart") {
|
if strings.Contains(r.URL.Path, "/market_chart") {
|
||||||
|
|
@ -479,7 +479,7 @@ func TestMarketCollector_Collect_Bad_WriteError(t *testing.T) {
|
||||||
|
|
||||||
// --- Market: EnsureDir error ---
|
// --- Market: EnsureDir error ---
|
||||||
|
|
||||||
func TestMarketCollector_Collect_Bad_EnsureDirError(t *testing.T) {
|
func TestMarketCollector_Collect_Bad_EnsureDirError_Good(t *testing.T) {
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
_ = json.NewEncoder(w).Encode(coinData{ID: "bitcoin"})
|
_ = json.NewEncoder(w).Encode(coinData{ID: "bitcoin"})
|
||||||
|
|
@ -502,7 +502,7 @@ func TestMarketCollector_Collect_Bad_EnsureDirError(t *testing.T) {
|
||||||
|
|
||||||
// --- Market: collectCurrent with limiter wait error ---
|
// --- Market: collectCurrent with limiter wait error ---
|
||||||
|
|
||||||
func TestMarketCollector_Collect_Bad_LimiterError(t *testing.T) {
|
func TestMarketCollector_Collect_Bad_LimiterError_Good(t *testing.T) {
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
_ = json.NewEncoder(w).Encode(coinData{ID: "bitcoin"})
|
_ = json.NewEncoder(w).Encode(coinData{ID: "bitcoin"})
|
||||||
|
|
@ -529,7 +529,7 @@ func TestMarketCollector_Collect_Bad_LimiterError(t *testing.T) {
|
||||||
|
|
||||||
// --- Market: collectHistorical with custom FromDate ---
|
// --- Market: collectHistorical with custom FromDate ---
|
||||||
|
|
||||||
func TestMarketCollector_Collect_Good_HistoricalCustomDate(t *testing.T) {
|
func TestMarketCollector_Collect_Good_HistoricalCustomDate_Good(t *testing.T) {
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
if strings.Contains(r.URL.Path, "/market_chart") {
|
if strings.Contains(r.URL.Path, "/market_chart") {
|
||||||
|
|
@ -564,7 +564,7 @@ func TestMarketCollector_Collect_Good_HistoricalCustomDate(t *testing.T) {
|
||||||
|
|
||||||
// --- BitcoinTalk: EnsureDir error ---
|
// --- BitcoinTalk: EnsureDir error ---
|
||||||
|
|
||||||
func TestBitcoinTalkCollector_Collect_Bad_EnsureDirError(t *testing.T) {
|
func TestBitcoinTalkCollector_Collect_Bad_EnsureDirError_Good(t *testing.T) {
|
||||||
em := &errorMedium{MockMedium: io.NewMockMedium(), ensureDirErr: testErr("mkdir denied")}
|
em := &errorMedium{MockMedium: io.NewMockMedium(), ensureDirErr: testErr("mkdir denied")}
|
||||||
cfg := &Config{Output: em, OutputDir: "/output", Dispatcher: NewDispatcher()}
|
cfg := &Config{Output: em, OutputDir: "/output", Dispatcher: NewDispatcher()}
|
||||||
cfg.Limiter = nil
|
cfg.Limiter = nil
|
||||||
|
|
@ -577,7 +577,7 @@ func TestBitcoinTalkCollector_Collect_Bad_EnsureDirError(t *testing.T) {
|
||||||
|
|
||||||
// --- BitcoinTalk: limiter error ---
|
// --- BitcoinTalk: limiter error ---
|
||||||
|
|
||||||
func TestBitcoinTalkCollector_Collect_Bad_LimiterError(t *testing.T) {
|
func TestBitcoinTalkCollector_Collect_Bad_LimiterError_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
cfg.Limiter = NewRateLimiter()
|
cfg.Limiter = NewRateLimiter()
|
||||||
|
|
@ -593,7 +593,7 @@ func TestBitcoinTalkCollector_Collect_Bad_LimiterError(t *testing.T) {
|
||||||
|
|
||||||
// --- BitcoinTalk: write error during post saving ---
|
// --- BitcoinTalk: write error during post saving ---
|
||||||
|
|
||||||
func TestBitcoinTalkCollector_Collect_Bad_WriteErrorOnPosts(t *testing.T) {
|
func TestBitcoinTalkCollector_Collect_Bad_WriteErrorOnPosts_Good(t *testing.T) {
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
_, _ = w.Write([]byte(sampleBTCTalkPage(3)))
|
_, _ = w.Write([]byte(sampleBTCTalkPage(3)))
|
||||||
|
|
@ -618,7 +618,7 @@ func TestBitcoinTalkCollector_Collect_Bad_WriteErrorOnPosts(t *testing.T) {
|
||||||
|
|
||||||
// --- BitcoinTalk: fetchPage with bad HTTP status ---
|
// --- BitcoinTalk: fetchPage with bad HTTP status ---
|
||||||
|
|
||||||
func TestBitcoinTalkCollector_FetchPage_Bad_NonOKStatus(t *testing.T) {
|
func TestBitcoinTalkCollector_FetchPage_Bad_NonOKStatus_Good(t *testing.T) {
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusForbidden)
|
w.WriteHeader(http.StatusForbidden)
|
||||||
}))
|
}))
|
||||||
|
|
@ -632,7 +632,7 @@ func TestBitcoinTalkCollector_FetchPage_Bad_NonOKStatus(t *testing.T) {
|
||||||
|
|
||||||
// --- BitcoinTalk: fetchPage with request error ---
|
// --- BitcoinTalk: fetchPage with request error ---
|
||||||
|
|
||||||
func TestBitcoinTalkCollector_FetchPage_Bad_RequestError(t *testing.T) {
|
func TestBitcoinTalkCollector_FetchPage_Bad_RequestError_Good(t *testing.T) {
|
||||||
old := httpClient
|
old := httpClient
|
||||||
httpClient = &http.Client{Transport: &rewriteTransport{target: "http://127.0.0.1:1"}} // Connection refused
|
httpClient = &http.Client{Transport: &rewriteTransport{target: "http://127.0.0.1:1"}} // Connection refused
|
||||||
defer func() { httpClient = old }()
|
defer func() { httpClient = old }()
|
||||||
|
|
@ -645,7 +645,7 @@ func TestBitcoinTalkCollector_FetchPage_Bad_RequestError(t *testing.T) {
|
||||||
|
|
||||||
// --- BitcoinTalk: fetchPage with valid but empty page ---
|
// --- BitcoinTalk: fetchPage with valid but empty page ---
|
||||||
|
|
||||||
func TestBitcoinTalkCollector_FetchPage_Good_EmptyPage(t *testing.T) {
|
func TestBitcoinTalkCollector_FetchPage_Good_EmptyPage_Good(t *testing.T) {
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
_, _ = w.Write([]byte("<html><body></body></html>"))
|
_, _ = w.Write([]byte("<html><body></body></html>"))
|
||||||
|
|
@ -664,7 +664,7 @@ func TestBitcoinTalkCollector_FetchPage_Good_EmptyPage(t *testing.T) {
|
||||||
|
|
||||||
// --- BitcoinTalk: Collect with fetch error + dispatcher ---
|
// --- BitcoinTalk: Collect with fetch error + dispatcher ---
|
||||||
|
|
||||||
func TestBitcoinTalkCollector_Collect_Bad_FetchErrorWithDispatcher(t *testing.T) {
|
func TestBitcoinTalkCollector_Collect_Bad_FetchErrorWithDispatcher_Good(t *testing.T) {
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
}))
|
}))
|
||||||
|
|
@ -691,7 +691,7 @@ func TestBitcoinTalkCollector_Collect_Bad_FetchErrorWithDispatcher(t *testing.T)
|
||||||
|
|
||||||
// --- State: Save with a populated state ---
|
// --- State: Save with a populated state ---
|
||||||
|
|
||||||
func TestState_Save_Good_RoundTrip(t *testing.T) {
|
func TestState_Save_Good_RoundTrip_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
s := NewState(m, "/data/state.json")
|
s := NewState(m, "/data/state.json")
|
||||||
|
|
||||||
|
|
@ -717,7 +717,7 @@ func TestState_Save_Good_RoundTrip(t *testing.T) {
|
||||||
|
|
||||||
// --- GitHub: Collect with Repo set triggers collectIssues/collectPRs (which fail via gh) ---
|
// --- GitHub: Collect with Repo set triggers collectIssues/collectPRs (which fail via gh) ---
|
||||||
|
|
||||||
func TestGitHubCollector_Collect_Bad_GhNotAuthenticated(t *testing.T) {
|
func TestGitHubCollector_Collect_Bad_GhNotAuthenticated_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
cfg.Limiter = nil
|
cfg.Limiter = nil
|
||||||
|
|
@ -737,7 +737,7 @@ func TestGitHubCollector_Collect_Bad_GhNotAuthenticated(t *testing.T) {
|
||||||
|
|
||||||
// --- GitHub: Collect IssuesOnly triggers only issues, not PRs ---
|
// --- GitHub: Collect IssuesOnly triggers only issues, not PRs ---
|
||||||
|
|
||||||
func TestGitHubCollector_Collect_Bad_IssuesOnlyGhFails(t *testing.T) {
|
func TestGitHubCollector_Collect_Bad_IssuesOnlyGhFails_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
cfg.Limiter = nil
|
cfg.Limiter = nil
|
||||||
|
|
@ -750,7 +750,7 @@ func TestGitHubCollector_Collect_Bad_IssuesOnlyGhFails(t *testing.T) {
|
||||||
|
|
||||||
// --- GitHub: Collect PRsOnly triggers only PRs, not issues ---
|
// --- GitHub: Collect PRsOnly triggers only PRs, not issues ---
|
||||||
|
|
||||||
func TestGitHubCollector_Collect_Bad_PRsOnlyGhFails(t *testing.T) {
|
func TestGitHubCollector_Collect_Bad_PRsOnlyGhFails_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
cfg.Limiter = nil
|
cfg.Limiter = nil
|
||||||
|
|
@ -763,7 +763,7 @@ func TestGitHubCollector_Collect_Bad_PRsOnlyGhFails(t *testing.T) {
|
||||||
|
|
||||||
// --- extractText: text before a br/p/div element adds newline ---
|
// --- extractText: text before a br/p/div element adds newline ---
|
||||||
|
|
||||||
func TestExtractText_Good_TextBeforeBR(t *testing.T) {
|
func TestExtractText_Good_TextBeforeBR_Good(t *testing.T) {
|
||||||
htmlStr := `<div class="inner">Hello<br>World<p>End</p></div>`
|
htmlStr := `<div class="inner">Hello<br>World<p>End</p></div>`
|
||||||
posts, err := ParsePostsFromHTML(fmt.Sprintf(`<html><body><div class="post"><div class="inner">%s</div></div></body></html>`,
|
posts, err := ParsePostsFromHTML(fmt.Sprintf(`<html><body><div class="post"><div class="inner">%s</div></div></body></html>`,
|
||||||
"First text<br>Second text<div>Third text</div>"))
|
"First text<br>Second text<div>Third text</div>"))
|
||||||
|
|
@ -777,7 +777,7 @@ func TestExtractText_Good_TextBeforeBR(t *testing.T) {
|
||||||
|
|
||||||
// --- ParsePostsFromHTML: posts with full structure ---
|
// --- ParsePostsFromHTML: posts with full structure ---
|
||||||
|
|
||||||
func TestParsePostsFromHTML_Good_FullStructure(t *testing.T) {
|
func TestParsePostsFromHTML_Good_FullStructure_Good(t *testing.T) {
|
||||||
htmlContent := `<html><body>
|
htmlContent := `<html><body>
|
||||||
<div class="post">
|
<div class="post">
|
||||||
<div class="poster_info">TestAuthor</div>
|
<div class="poster_info">TestAuthor</div>
|
||||||
|
|
@ -796,7 +796,7 @@ func TestParsePostsFromHTML_Good_FullStructure(t *testing.T) {
|
||||||
|
|
||||||
// --- getChildrenText: nested element node path ---
|
// --- getChildrenText: nested element node path ---
|
||||||
|
|
||||||
func TestHTMLToMarkdown_Good_NestedElements(t *testing.T) {
|
func TestHTMLToMarkdown_Good_NestedElements_Good(t *testing.T) {
|
||||||
// <a> with nested <span> triggers getChildrenText with non-text child nodes
|
// <a> with nested <span> triggers getChildrenText with non-text child nodes
|
||||||
input := `<p><a href="https://example.com"><span>Nested</span> Link</a></p>`
|
input := `<p><a href="https://example.com"><span>Nested</span> Link</a></p>`
|
||||||
result, err := HTMLToMarkdown(input)
|
result, err := HTMLToMarkdown(input)
|
||||||
|
|
@ -806,7 +806,7 @@ func TestHTMLToMarkdown_Good_NestedElements(t *testing.T) {
|
||||||
|
|
||||||
// --- HTML: ordered list ---
|
// --- HTML: ordered list ---
|
||||||
|
|
||||||
func TestHTMLToMarkdown_Good_OL(t *testing.T) {
|
func TestHTMLToMarkdown_Good_OL_Good(t *testing.T) {
|
||||||
input := `<ol><li>First</li><li>Second</li></ol>`
|
input := `<ol><li>First</li><li>Second</li></ol>`
|
||||||
result, err := HTMLToMarkdown(input)
|
result, err := HTMLToMarkdown(input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -816,7 +816,7 @@ func TestHTMLToMarkdown_Good_OL(t *testing.T) {
|
||||||
|
|
||||||
// --- HTML: blockquote ---
|
// --- HTML: blockquote ---
|
||||||
|
|
||||||
func TestHTMLToMarkdown_Good_BlockquoteElement(t *testing.T) {
|
func TestHTMLToMarkdown_Good_BlockquoteElement_Good(t *testing.T) {
|
||||||
input := `<blockquote>Quoted text</blockquote>`
|
input := `<blockquote>Quoted text</blockquote>`
|
||||||
result, err := HTMLToMarkdown(input)
|
result, err := HTMLToMarkdown(input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -825,7 +825,7 @@ func TestHTMLToMarkdown_Good_BlockquoteElement(t *testing.T) {
|
||||||
|
|
||||||
// --- HTML: hr ---
|
// --- HTML: hr ---
|
||||||
|
|
||||||
func TestHTMLToMarkdown_Good_HR(t *testing.T) {
|
func TestHTMLToMarkdown_Good_HR_Good(t *testing.T) {
|
||||||
input := `<p>Before</p><hr><p>After</p>`
|
input := `<p>Before</p><hr><p>After</p>`
|
||||||
result, err := HTMLToMarkdown(input)
|
result, err := HTMLToMarkdown(input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -834,7 +834,7 @@ func TestHTMLToMarkdown_Good_HR(t *testing.T) {
|
||||||
|
|
||||||
// --- HTML: h4, h5, h6 ---
|
// --- HTML: h4, h5, h6 ---
|
||||||
|
|
||||||
func TestHTMLToMarkdown_Good_AllHeadingLevels(t *testing.T) {
|
func TestHTMLToMarkdown_Good_AllHeadingLevels_Good(t *testing.T) {
|
||||||
input := `<h4>H4</h4><h5>H5</h5><h6>H6</h6>`
|
input := `<h4>H4</h4><h5>H5</h5><h6>H6</h6>`
|
||||||
result, err := HTMLToMarkdown(input)
|
result, err := HTMLToMarkdown(input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -845,7 +845,7 @@ func TestHTMLToMarkdown_Good_AllHeadingLevels(t *testing.T) {
|
||||||
|
|
||||||
// --- HTML: link without href ---
|
// --- HTML: link without href ---
|
||||||
|
|
||||||
func TestHTMLToMarkdown_Good_LinkNoHref(t *testing.T) {
|
func TestHTMLToMarkdown_Good_LinkNoHref_Good(t *testing.T) {
|
||||||
input := `<a>bare link text</a>`
|
input := `<a>bare link text</a>`
|
||||||
result, err := HTMLToMarkdown(input)
|
result, err := HTMLToMarkdown(input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -855,7 +855,7 @@ func TestHTMLToMarkdown_Good_LinkNoHref(t *testing.T) {
|
||||||
|
|
||||||
// --- HTML: unordered list ---
|
// --- HTML: unordered list ---
|
||||||
|
|
||||||
func TestHTMLToMarkdown_Good_UL(t *testing.T) {
|
func TestHTMLToMarkdown_Good_UL_Good(t *testing.T) {
|
||||||
input := `<ul><li>Item A</li><li>Item B</li></ul>`
|
input := `<ul><li>Item A</li><li>Item B</li></ul>`
|
||||||
result, err := HTMLToMarkdown(input)
|
result, err := HTMLToMarkdown(input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -865,7 +865,7 @@ func TestHTMLToMarkdown_Good_UL(t *testing.T) {
|
||||||
|
|
||||||
// --- HTML: br tag ---
|
// --- HTML: br tag ---
|
||||||
|
|
||||||
func TestHTMLToMarkdown_Good_BRTag(t *testing.T) {
|
func TestHTMLToMarkdown_Good_BRTag_Good(t *testing.T) {
|
||||||
input := `<p>Line one<br>Line two</p>`
|
input := `<p>Line one<br>Line two</p>`
|
||||||
result, err := HTMLToMarkdown(input)
|
result, err := HTMLToMarkdown(input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -875,7 +875,7 @@ func TestHTMLToMarkdown_Good_BRTag(t *testing.T) {
|
||||||
|
|
||||||
// --- HTML: style tag stripped ---
|
// --- HTML: style tag stripped ---
|
||||||
|
|
||||||
func TestHTMLToMarkdown_Good_StyleStripped(t *testing.T) {
|
func TestHTMLToMarkdown_Good_StyleStripped_Good(t *testing.T) {
|
||||||
input := `<html><head><style>body{color:red}</style></head><body><p>Clean</p></body></html>`
|
input := `<html><head><style>body{color:red}</style></head><body><p>Clean</p></body></html>`
|
||||||
result, err := HTMLToMarkdown(input)
|
result, err := HTMLToMarkdown(input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -885,7 +885,7 @@ func TestHTMLToMarkdown_Good_StyleStripped(t *testing.T) {
|
||||||
|
|
||||||
// --- HTML: i and b tags ---
|
// --- HTML: i and b tags ---
|
||||||
|
|
||||||
func TestHTMLToMarkdown_Good_AlternateBoldItalic(t *testing.T) {
|
func TestHTMLToMarkdown_Good_AlternateBoldItalic_Good(t *testing.T) {
|
||||||
input := `<p><b>bold</b> and <i>italic</i></p>`
|
input := `<p><b>bold</b> and <i>italic</i></p>`
|
||||||
result, err := HTMLToMarkdown(input)
|
result, err := HTMLToMarkdown(input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -895,7 +895,7 @@ func TestHTMLToMarkdown_Good_AlternateBoldItalic(t *testing.T) {
|
||||||
|
|
||||||
// --- Market: collectCurrent with limiter that actually blocks ---
|
// --- Market: collectCurrent with limiter that actually blocks ---
|
||||||
|
|
||||||
func TestMarketCollector_Collect_Bad_LimiterBlocksThenCancelled(t *testing.T) {
|
func TestMarketCollector_Collect_Bad_LimiterBlocksThenCancelled_Good(t *testing.T) {
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
_ = json.NewEncoder(w).Encode(coinData{ID: "bitcoin", Symbol: "btc", Name: "Bitcoin",
|
_ = json.NewEncoder(w).Encode(coinData{ID: "bitcoin", Symbol: "btc", Name: "Bitcoin",
|
||||||
|
|
@ -927,7 +927,7 @@ func TestMarketCollector_Collect_Bad_LimiterBlocksThenCancelled(t *testing.T) {
|
||||||
|
|
||||||
// --- Papers: IACR with limiter that blocks ---
|
// --- Papers: IACR with limiter that blocks ---
|
||||||
|
|
||||||
func TestPapersCollector_CollectIACR_Bad_LimiterBlocks(t *testing.T) {
|
func TestPapersCollector_CollectIACR_Bad_LimiterBlocks_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
cfg.Limiter = NewRateLimiter()
|
cfg.Limiter = NewRateLimiter()
|
||||||
|
|
@ -944,7 +944,7 @@ func TestPapersCollector_CollectIACR_Bad_LimiterBlocks(t *testing.T) {
|
||||||
|
|
||||||
// --- Papers: arXiv with limiter that blocks ---
|
// --- Papers: arXiv with limiter that blocks ---
|
||||||
|
|
||||||
func TestPapersCollector_CollectArXiv_Bad_LimiterBlocks(t *testing.T) {
|
func TestPapersCollector_CollectArXiv_Bad_LimiterBlocks_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
cfg.Limiter = NewRateLimiter()
|
cfg.Limiter = NewRateLimiter()
|
||||||
|
|
@ -961,7 +961,7 @@ func TestPapersCollector_CollectArXiv_Bad_LimiterBlocks(t *testing.T) {
|
||||||
|
|
||||||
// --- BitcoinTalk: limiter that blocks ---
|
// --- BitcoinTalk: limiter that blocks ---
|
||||||
|
|
||||||
func TestBitcoinTalkCollector_Collect_Bad_LimiterBlocks(t *testing.T) {
|
func TestBitcoinTalkCollector_Collect_Bad_LimiterBlocks_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
cfg.Limiter = NewRateLimiter()
|
cfg.Limiter = NewRateLimiter()
|
||||||
|
|
@ -1021,7 +1021,7 @@ func (w *writeCountMedium) Exists(path string) bool { return w.MockMedium.Exists
|
||||||
func (w *writeCountMedium) IsDir(path string) bool { return w.MockMedium.IsDir(path) }
|
func (w *writeCountMedium) IsDir(path string) bool { return w.MockMedium.IsDir(path) }
|
||||||
|
|
||||||
// Test that the summary.md write error in collectCurrent is handled.
|
// Test that the summary.md write error in collectCurrent is handled.
|
||||||
func TestMarketCollector_Collect_Bad_SummaryWriteError(t *testing.T) {
|
func TestMarketCollector_Collect_Bad_SummaryWriteError_Good(t *testing.T) {
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
if strings.Contains(r.URL.Path, "/market_chart") {
|
if strings.Contains(r.URL.Path, "/market_chart") {
|
||||||
|
|
@ -1058,7 +1058,7 @@ func TestMarketCollector_Collect_Bad_SummaryWriteError(t *testing.T) {
|
||||||
|
|
||||||
// --- Market: collectHistorical write error ---
|
// --- Market: collectHistorical write error ---
|
||||||
|
|
||||||
func TestMarketCollector_Collect_Bad_HistoricalWriteError(t *testing.T) {
|
func TestMarketCollector_Collect_Bad_HistoricalWriteError_Good(t *testing.T) {
|
||||||
callCount := 0
|
callCount := 0
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
callCount++
|
callCount++
|
||||||
|
|
@ -1097,7 +1097,7 @@ func TestMarketCollector_Collect_Bad_HistoricalWriteError(t *testing.T) {
|
||||||
|
|
||||||
// --- State: Save write error ---
|
// --- State: Save write error ---
|
||||||
|
|
||||||
func TestState_Save_Bad_WriteError(t *testing.T) {
|
func TestState_Save_Bad_WriteError_Good(t *testing.T) {
|
||||||
em := &errorMedium{MockMedium: io.NewMockMedium(), writeErr: testErr("disk full")}
|
em := &errorMedium{MockMedium: io.NewMockMedium(), writeErr: testErr("disk full")}
|
||||||
s := NewState(em, "/state.json")
|
s := NewState(em, "/state.json")
|
||||||
s.Set("test", &StateEntry{Source: "test", Items: 1})
|
s.Set("test", &StateEntry{Source: "test", Items: 1})
|
||||||
|
|
@ -1109,7 +1109,7 @@ func TestState_Save_Bad_WriteError(t *testing.T) {
|
||||||
|
|
||||||
// --- Excavator: collector with state error ---
|
// --- Excavator: collector with state error ---
|
||||||
|
|
||||||
func TestExcavator_Run_Bad_CollectorStateError(t *testing.T) {
|
func TestExcavator_Run_Bad_CollectorStateError_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
cfg.State = NewState(m, "/state.json")
|
cfg.State = NewState(m, "/state.json")
|
||||||
|
|
@ -1131,7 +1131,7 @@ func TestExcavator_Run_Bad_CollectorStateError(t *testing.T) {
|
||||||
|
|
||||||
// --- BitcoinTalk: page returns zero posts (empty content) ---
|
// --- BitcoinTalk: page returns zero posts (empty content) ---
|
||||||
|
|
||||||
func TestBitcoinTalkCollector_Collect_Good_ZeroPostsPage(t *testing.T) {
|
func TestBitcoinTalkCollector_Collect_Good_ZeroPostsPage_Good(t *testing.T) {
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
// Valid HTML with no post divs at all
|
// Valid HTML with no post divs at all
|
||||||
|
|
@ -1156,7 +1156,7 @@ func TestBitcoinTalkCollector_Collect_Good_ZeroPostsPage(t *testing.T) {
|
||||||
|
|
||||||
// --- Excavator: state save error after collection ---
|
// --- Excavator: state save error after collection ---
|
||||||
|
|
||||||
func TestExcavator_Run_Bad_StateSaveError(t *testing.T) {
|
func TestExcavator_Run_Bad_StateSaveError_Good(t *testing.T) {
|
||||||
em := &errorMedium{MockMedium: io.NewMockMedium(), writeErr: testErr("state write failed")}
|
em := &errorMedium{MockMedium: io.NewMockMedium(), writeErr: testErr("state write failed")}
|
||||||
cfg := &Config{
|
cfg := &Config{
|
||||||
Output: io.NewMockMedium(), // Use regular medium for output
|
Output: io.NewMockMedium(), // Use regular medium for output
|
||||||
|
|
@ -1180,7 +1180,7 @@ func TestExcavator_Run_Bad_StateSaveError(t *testing.T) {
|
||||||
|
|
||||||
// --- State: Load with read error ---
|
// --- State: Load with read error ---
|
||||||
|
|
||||||
func TestState_Load_Bad_ReadError(t *testing.T) {
|
func TestState_Load_Bad_ReadError_Good(t *testing.T) {
|
||||||
em := &errorMedium{MockMedium: io.NewMockMedium(), readErr: testErr("read denied")}
|
em := &errorMedium{MockMedium: io.NewMockMedium(), readErr: testErr("read denied")}
|
||||||
em.MockMedium.Files["/state.json"] = "{}" // File exists but read will fail
|
em.MockMedium.Files["/state.json"] = "{}" // File exists but read will fail
|
||||||
|
|
||||||
|
|
@ -1192,7 +1192,7 @@ func TestState_Load_Bad_ReadError(t *testing.T) {
|
||||||
|
|
||||||
// --- Papers: PaperSourceAll emits complete ---
|
// --- Papers: PaperSourceAll emits complete ---
|
||||||
|
|
||||||
func TestPapersCollector_CollectAll_Good_ArxivFailsWithIACR(t *testing.T) {
|
func TestPapersCollector_CollectAll_Good_ArxivFailsWithIACR_Good(t *testing.T) {
|
||||||
callCount := 0
|
callCount := 0
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
callCount++
|
callCount++
|
||||||
|
|
@ -1229,7 +1229,7 @@ func TestPapersCollector_CollectAll_Good_ArxivFailsWithIACR(t *testing.T) {
|
||||||
|
|
||||||
// --- Papers: IACR with cancelled context (request creation fails) ---
|
// --- Papers: IACR with cancelled context (request creation fails) ---
|
||||||
|
|
||||||
func TestPapersCollector_CollectIACR_Bad_CancelledContextRequestFails(t *testing.T) {
|
func TestPapersCollector_CollectIACR_Bad_CancelledContextRequestFails_Good(t *testing.T) {
|
||||||
// Don't set up any server - the request should fail because context is cancelled.
|
// Don't set up any server - the request should fail because context is cancelled.
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
|
|
@ -1245,7 +1245,7 @@ func TestPapersCollector_CollectIACR_Bad_CancelledContextRequestFails(t *testing
|
||||||
|
|
||||||
// --- Papers: arXiv with cancelled context ---
|
// --- Papers: arXiv with cancelled context ---
|
||||||
|
|
||||||
func TestPapersCollector_CollectArXiv_Bad_CancelledContextRequestFails(t *testing.T) {
|
func TestPapersCollector_CollectArXiv_Bad_CancelledContextRequestFails_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
cfg.Limiter = nil
|
cfg.Limiter = nil
|
||||||
|
|
@ -1260,7 +1260,7 @@ func TestPapersCollector_CollectArXiv_Bad_CancelledContextRequestFails(t *testin
|
||||||
|
|
||||||
// --- Market: collectHistorical limiter blocks ---
|
// --- Market: collectHistorical limiter blocks ---
|
||||||
|
|
||||||
func TestMarketCollector_Collect_Bad_HistoricalLimiterBlocks(t *testing.T) {
|
func TestMarketCollector_Collect_Bad_HistoricalLimiterBlocks_Good(t *testing.T) {
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
_ = json.NewEncoder(w).Encode(coinData{
|
_ = json.NewEncoder(w).Encode(coinData{
|
||||||
|
|
@ -1299,7 +1299,7 @@ func TestMarketCollector_Collect_Bad_HistoricalLimiterBlocks(t *testing.T) {
|
||||||
|
|
||||||
// --- BitcoinTalk: fetchPage with invalid URL ---
|
// --- BitcoinTalk: fetchPage with invalid URL ---
|
||||||
|
|
||||||
func TestBitcoinTalkCollector_FetchPage_Bad_InvalidURL(t *testing.T) {
|
func TestBitcoinTalkCollector_FetchPage_Bad_InvalidURL_Good(t *testing.T) {
|
||||||
b := &BitcoinTalkCollector{TopicID: "12345"}
|
b := &BitcoinTalkCollector{TopicID: "12345"}
|
||||||
// Use a URL with control character that will fail NewRequestWithContext
|
// Use a URL with control character that will fail NewRequestWithContext
|
||||||
_, err := b.fetchPage(context.Background(), "http://\x7f/invalid")
|
_, err := b.fetchPage(context.Background(), "http://\x7f/invalid")
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ func TestDispatcher_On_Good(t *testing.T) {
|
||||||
assert.Equal(t, 3, count, "All three handlers should be called")
|
assert.Equal(t, 3, count, "All three handlers should be called")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDispatcher_Emit_Good_NoHandlers(t *testing.T) {
|
func TestDispatcher_Emit_Good_NoHandlers_Good(t *testing.T) {
|
||||||
d := NewDispatcher()
|
d := NewDispatcher()
|
||||||
|
|
||||||
// Should not panic when emitting an event with no handlers
|
// Should not panic when emitting an event with no handlers
|
||||||
|
|
@ -56,7 +56,7 @@ func TestDispatcher_Emit_Good_NoHandlers(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDispatcher_Emit_Good_MultipleEventTypes(t *testing.T) {
|
func TestDispatcher_Emit_Good_MultipleEventTypes_Good(t *testing.T) {
|
||||||
d := NewDispatcher()
|
d := NewDispatcher()
|
||||||
|
|
||||||
var starts, errors int
|
var starts, errors int
|
||||||
|
|
@ -71,7 +71,7 @@ func TestDispatcher_Emit_Good_MultipleEventTypes(t *testing.T) {
|
||||||
assert.Equal(t, 1, errors)
|
assert.Equal(t, 1, errors)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDispatcher_Emit_Good_SetsTime(t *testing.T) {
|
func TestDispatcher_Emit_Good_SetsTime_Good(t *testing.T) {
|
||||||
d := NewDispatcher()
|
d := NewDispatcher()
|
||||||
|
|
||||||
var received Event
|
var received Event
|
||||||
|
|
@ -87,7 +87,7 @@ func TestDispatcher_Emit_Good_SetsTime(t *testing.T) {
|
||||||
assert.True(t, received.Time.Before(after) || received.Time.Equal(after))
|
assert.True(t, received.Time.Before(after) || received.Time.Equal(after))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDispatcher_Emit_Good_PreservesExistingTime(t *testing.T) {
|
func TestDispatcher_Emit_Good_PreservesExistingTime_Good(t *testing.T) {
|
||||||
d := NewDispatcher()
|
d := NewDispatcher()
|
||||||
|
|
||||||
customTime := time.Date(2025, 6, 15, 12, 0, 0, 0, time.UTC)
|
customTime := time.Date(2025, 6, 15, 12, 0, 0, 0, time.UTC)
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestExcavator_Run_Good_ResumeSkipsCompleted(t *testing.T) {
|
func TestExcavator_Run_Good_ResumeSkipsCompleted_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
cfg.Limiter = nil
|
cfg.Limiter = nil
|
||||||
|
|
@ -41,7 +41,7 @@ func TestExcavator_Run_Good_ResumeSkipsCompleted(t *testing.T) {
|
||||||
assert.Equal(t, 1, result.Skipped)
|
assert.Equal(t, 1, result.Skipped)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExcavator_Run_Good_ResumeRunsIncomplete(t *testing.T) {
|
func TestExcavator_Run_Good_ResumeRunsIncomplete_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
cfg.Limiter = nil
|
cfg.Limiter = nil
|
||||||
|
|
@ -67,7 +67,7 @@ func TestExcavator_Run_Good_ResumeRunsIncomplete(t *testing.T) {
|
||||||
assert.Equal(t, 5, result.Items)
|
assert.Equal(t, 5, result.Items)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExcavator_Run_Good_NilState(t *testing.T) {
|
func TestExcavator_Run_Good_NilState_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
cfg.State = nil
|
cfg.State = nil
|
||||||
|
|
@ -85,7 +85,7 @@ func TestExcavator_Run_Good_NilState(t *testing.T) {
|
||||||
assert.Equal(t, 3, result.Items)
|
assert.Equal(t, 3, result.Items)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExcavator_Run_Good_NilDispatcher(t *testing.T) {
|
func TestExcavator_Run_Good_NilDispatcher_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
cfg.Dispatcher = nil
|
cfg.Dispatcher = nil
|
||||||
|
|
@ -103,7 +103,7 @@ func TestExcavator_Run_Good_NilDispatcher(t *testing.T) {
|
||||||
assert.Equal(t, 2, result.Items)
|
assert.Equal(t, 2, result.Items)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExcavator_Run_Good_ProgressEvents(t *testing.T) {
|
func TestExcavator_Run_Good_ProgressEvents_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
cfg.Limiter = nil
|
cfg.Limiter = nil
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ func TestExcavator_Run_Good(t *testing.T) {
|
||||||
assert.Len(t, result.Files, 8)
|
assert.Len(t, result.Files, 8)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExcavator_Run_Good_Empty(t *testing.T) {
|
func TestExcavator_Run_Good_Empty_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
|
|
||||||
|
|
@ -77,7 +77,7 @@ func TestExcavator_Run_Good_Empty(t *testing.T) {
|
||||||
assert.Equal(t, 0, result.Items)
|
assert.Equal(t, 0, result.Items)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExcavator_Run_Good_DryRun(t *testing.T) {
|
func TestExcavator_Run_Good_DryRun_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
cfg.DryRun = true
|
cfg.DryRun = true
|
||||||
|
|
@ -98,7 +98,7 @@ func TestExcavator_Run_Good_DryRun(t *testing.T) {
|
||||||
assert.Equal(t, 0, result.Items)
|
assert.Equal(t, 0, result.Items)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExcavator_Run_Good_ScanOnly(t *testing.T) {
|
func TestExcavator_Run_Good_ScanOnly_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
|
|
||||||
|
|
@ -123,7 +123,7 @@ func TestExcavator_Run_Good_ScanOnly(t *testing.T) {
|
||||||
assert.Contains(t, progressMessages[0], "source-a")
|
assert.Contains(t, progressMessages[0], "source-a")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExcavator_Run_Good_WithErrors(t *testing.T) {
|
func TestExcavator_Run_Good_WithErrors_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
cfg.Limiter = nil
|
cfg.Limiter = nil
|
||||||
|
|
@ -146,7 +146,7 @@ func TestExcavator_Run_Good_WithErrors(t *testing.T) {
|
||||||
assert.True(t, c3.called)
|
assert.True(t, c3.called)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExcavator_Run_Good_CancelledContext(t *testing.T) {
|
func TestExcavator_Run_Good_CancelledContext_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
|
|
||||||
|
|
@ -163,7 +163,7 @@ func TestExcavator_Run_Good_CancelledContext(t *testing.T) {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExcavator_Run_Good_SavesState(t *testing.T) {
|
func TestExcavator_Run_Good_SavesState_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
cfg.Limiter = nil
|
cfg.Limiter = nil
|
||||||
|
|
@ -184,7 +184,7 @@ func TestExcavator_Run_Good_SavesState(t *testing.T) {
|
||||||
assert.Equal(t, "source-a", entry.Source)
|
assert.Equal(t, "source-a", entry.Source)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExcavator_Run_Good_Events(t *testing.T) {
|
func TestExcavator_Run_Good_Events_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
cfg.Limiter = nil
|
cfg.Limiter = nil
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,12 @@ func TestGitHubCollector_Name_Good(t *testing.T) {
|
||||||
assert.Equal(t, "github:host-uk/core", g.Name())
|
assert.Equal(t, "github:host-uk/core", g.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGitHubCollector_Name_Good_OrgOnly(t *testing.T) {
|
func TestGitHubCollector_Name_Good_OrgOnly_Good(t *testing.T) {
|
||||||
g := &GitHubCollector{Org: "host-uk"}
|
g := &GitHubCollector{Org: "host-uk"}
|
||||||
assert.Equal(t, "github:host-uk", g.Name())
|
assert.Equal(t, "github:host-uk", g.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGitHubCollector_Collect_Good_DryRun(t *testing.T) {
|
func TestGitHubCollector_Collect_Good_DryRun_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
cfg.DryRun = true
|
cfg.DryRun = true
|
||||||
|
|
@ -40,7 +40,7 @@ func TestGitHubCollector_Collect_Good_DryRun(t *testing.T) {
|
||||||
assert.True(t, progressEmitted, "Should emit progress event in dry-run mode")
|
assert.True(t, progressEmitted, "Should emit progress event in dry-run mode")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGitHubCollector_Collect_Good_DryRun_IssuesOnly(t *testing.T) {
|
func TestGitHubCollector_Collect_Good_DryRun_IssuesOnly_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
cfg.DryRun = true
|
cfg.DryRun = true
|
||||||
|
|
@ -52,7 +52,7 @@ func TestGitHubCollector_Collect_Good_DryRun_IssuesOnly(t *testing.T) {
|
||||||
assert.Equal(t, 0, result.Items)
|
assert.Equal(t, 0, result.Items)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGitHubCollector_Collect_Good_DryRun_PRsOnly(t *testing.T) {
|
func TestGitHubCollector_Collect_Good_DryRun_PRsOnly_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
cfg.DryRun = true
|
cfg.DryRun = true
|
||||||
|
|
@ -90,7 +90,7 @@ func TestFormatIssueMarkdown_Good(t *testing.T) {
|
||||||
assert.Contains(t, md, "**URL:** https://github.com/test/repo/issues/42")
|
assert.Contains(t, md, "**URL:** https://github.com/test/repo/issues/42")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFormatIssueMarkdown_Good_NoLabels(t *testing.T) {
|
func TestFormatIssueMarkdown_Good_NoLabels_Good(t *testing.T) {
|
||||||
issue := ghIssue{
|
issue := ghIssue{
|
||||||
Number: 1,
|
Number: 1,
|
||||||
Title: "Simple",
|
Title: "Simple",
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMarketCollector_Collect_Good_HistoricalWithFromDate(t *testing.T) {
|
func TestMarketCollector_Collect_Good_HistoricalWithFromDate_Good(t *testing.T) {
|
||||||
callCount := 0
|
callCount := 0
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
callCount++
|
callCount++
|
||||||
|
|
@ -58,7 +58,7 @@ func TestMarketCollector_Collect_Good_HistoricalWithFromDate(t *testing.T) {
|
||||||
assert.Equal(t, 3, result.Items)
|
assert.Equal(t, 3, result.Items)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMarketCollector_Collect_Good_HistoricalInvalidDate(t *testing.T) {
|
func TestMarketCollector_Collect_Good_HistoricalInvalidDate_Good(t *testing.T) {
|
||||||
callCount := 0
|
callCount := 0
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
callCount++
|
callCount++
|
||||||
|
|
@ -100,7 +100,7 @@ func TestMarketCollector_Collect_Good_HistoricalInvalidDate(t *testing.T) {
|
||||||
assert.Equal(t, 3, result.Items)
|
assert.Equal(t, 3, result.Items)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMarketCollector_Collect_Bad_HistoricalServerError(t *testing.T) {
|
func TestMarketCollector_Collect_Bad_HistoricalServerError_Good(t *testing.T) {
|
||||||
callCount := 0
|
callCount := 0
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
callCount++
|
callCount++
|
||||||
|
|
@ -139,7 +139,7 @@ func TestMarketCollector_Collect_Bad_HistoricalServerError(t *testing.T) {
|
||||||
assert.Equal(t, 1, result.Errors) // historical failed
|
assert.Equal(t, 1, result.Errors) // historical failed
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMarketCollector_Collect_Good_EmitsEvents(t *testing.T) {
|
func TestMarketCollector_Collect_Good_EmitsEvents_Good(t *testing.T) {
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
data := coinData{
|
data := coinData{
|
||||||
|
|
@ -174,7 +174,7 @@ func TestMarketCollector_Collect_Good_EmitsEvents(t *testing.T) {
|
||||||
assert.Equal(t, 1, completes)
|
assert.Equal(t, 1, completes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMarketCollector_Collect_Good_CancelledContext(t *testing.T) {
|
func TestMarketCollector_Collect_Good_CancelledContext_Good(t *testing.T) {
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
}))
|
}))
|
||||||
|
|
@ -199,7 +199,7 @@ func TestMarketCollector_Collect_Good_CancelledContext(t *testing.T) {
|
||||||
assert.Equal(t, 1, result.Errors)
|
assert.Equal(t, 1, result.Errors)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFormatMarketSummary_Good_AllFields(t *testing.T) {
|
func TestFormatMarketSummary_Good_AllFields_Good(t *testing.T) {
|
||||||
data := &coinData{
|
data := &coinData{
|
||||||
Name: "Lethean",
|
Name: "Lethean",
|
||||||
Symbol: "lthn",
|
Symbol: "lthn",
|
||||||
|
|
@ -231,7 +231,7 @@ func TestFormatMarketSummary_Good_AllFields(t *testing.T) {
|
||||||
assert.Contains(t, summary, "Last updated")
|
assert.Contains(t, summary, "Last updated")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFormatMarketSummary_Good_Minimal(t *testing.T) {
|
func TestFormatMarketSummary_Good_Minimal_Good(t *testing.T) {
|
||||||
data := &coinData{
|
data := &coinData{
|
||||||
Name: "Unknown",
|
Name: "Unknown",
|
||||||
Symbol: "ukn",
|
Symbol: "ukn",
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ func TestMarketCollector_Name_Good(t *testing.T) {
|
||||||
assert.Equal(t, "market:bitcoin", m.Name())
|
assert.Equal(t, "market:bitcoin", m.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMarketCollector_Collect_Bad_NoCoinID(t *testing.T) {
|
func TestMarketCollector_Collect_Bad_NoCoinID_Good(t *testing.T) {
|
||||||
mock := io.NewMockMedium()
|
mock := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(mock, "/output")
|
cfg := NewConfigWithMedium(mock, "/output")
|
||||||
|
|
||||||
|
|
@ -27,7 +27,7 @@ func TestMarketCollector_Collect_Bad_NoCoinID(t *testing.T) {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMarketCollector_Collect_Good_DryRun(t *testing.T) {
|
func TestMarketCollector_Collect_Good_DryRun_Good(t *testing.T) {
|
||||||
mock := io.NewMockMedium()
|
mock := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(mock, "/output")
|
cfg := NewConfigWithMedium(mock, "/output")
|
||||||
cfg.DryRun = true
|
cfg.DryRun = true
|
||||||
|
|
@ -39,7 +39,7 @@ func TestMarketCollector_Collect_Good_DryRun(t *testing.T) {
|
||||||
assert.Equal(t, 0, result.Items)
|
assert.Equal(t, 0, result.Items)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMarketCollector_Collect_Good_CurrentData(t *testing.T) {
|
func TestMarketCollector_Collect_Good_CurrentData_Good(t *testing.T) {
|
||||||
// Set up a mock CoinGecko server
|
// Set up a mock CoinGecko server
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
data := coinData{
|
data := coinData{
|
||||||
|
|
@ -94,7 +94,7 @@ func TestMarketCollector_Collect_Good_CurrentData(t *testing.T) {
|
||||||
assert.Contains(t, summary, "42000.50")
|
assert.Contains(t, summary, "42000.50")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMarketCollector_Collect_Good_Historical(t *testing.T) {
|
func TestMarketCollector_Collect_Good_Historical_Good(t *testing.T) {
|
||||||
callCount := 0
|
callCount := 0
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
callCount++
|
callCount++
|
||||||
|
|
@ -166,7 +166,7 @@ func TestFormatMarketSummary_Good(t *testing.T) {
|
||||||
assert.Contains(t, summary, "Total Supply")
|
assert.Contains(t, summary, "Total Supply")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMarketCollector_Collect_Bad_ServerError(t *testing.T) {
|
func TestMarketCollector_Collect_Bad_ServerError_Good(t *testing.T) {
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
}))
|
}))
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,7 @@ func TestPapersCollector_CollectArXiv_Good(t *testing.T) {
|
||||||
assert.Contains(t, content, "Alice")
|
assert.Contains(t, content, "Alice")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPapersCollector_CollectArXiv_Good_WithCategory(t *testing.T) {
|
func TestPapersCollector_CollectArXiv_Good_WithCategory_Good(t *testing.T) {
|
||||||
var capturedQuery string
|
var capturedQuery string
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
capturedQuery = r.URL.RawQuery
|
capturedQuery = r.URL.RawQuery
|
||||||
|
|
@ -167,7 +167,7 @@ func TestPapersCollector_CollectAll_Good(t *testing.T) {
|
||||||
assert.Equal(t, 4, result.Items) // 2 IACR + 2 arXiv
|
assert.Equal(t, 4, result.Items) // 2 IACR + 2 arXiv
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPapersCollector_CollectIACR_Bad_ServerError(t *testing.T) {
|
func TestPapersCollector_CollectIACR_Bad_ServerError_Good(t *testing.T) {
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
}))
|
}))
|
||||||
|
|
@ -187,7 +187,7 @@ func TestPapersCollector_CollectIACR_Bad_ServerError(t *testing.T) {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPapersCollector_CollectArXiv_Bad_ServerError(t *testing.T) {
|
func TestPapersCollector_CollectArXiv_Bad_ServerError_Good(t *testing.T) {
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusServiceUnavailable)
|
w.WriteHeader(http.StatusServiceUnavailable)
|
||||||
}))
|
}))
|
||||||
|
|
@ -207,7 +207,7 @@ func TestPapersCollector_CollectArXiv_Bad_ServerError(t *testing.T) {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPapersCollector_CollectArXiv_Bad_InvalidXML(t *testing.T) {
|
func TestPapersCollector_CollectArXiv_Bad_InvalidXML_Good(t *testing.T) {
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/xml")
|
w.Header().Set("Content-Type", "application/xml")
|
||||||
_, _ = w.Write([]byte(`not xml at all`))
|
_, _ = w.Write([]byte(`not xml at all`))
|
||||||
|
|
@ -228,7 +228,7 @@ func TestPapersCollector_CollectArXiv_Bad_InvalidXML(t *testing.T) {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPapersCollector_CollectAll_Bad_BothFail(t *testing.T) {
|
func TestPapersCollector_CollectAll_Bad_BothFail_Good(t *testing.T) {
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
}))
|
}))
|
||||||
|
|
@ -248,7 +248,7 @@ func TestPapersCollector_CollectAll_Bad_BothFail(t *testing.T) {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPapersCollector_CollectAll_Good_OneFails(t *testing.T) {
|
func TestPapersCollector_CollectAll_Good_OneFails_Good(t *testing.T) {
|
||||||
callCount := 0
|
callCount := 0
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
callCount++
|
callCount++
|
||||||
|
|
@ -297,7 +297,7 @@ func TestExtractIACRPapers_Good(t *testing.T) {
|
||||||
assert.Equal(t, "Lattice Cryptography", papers[1].Title)
|
assert.Equal(t, "Lattice Cryptography", papers[1].Title)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExtractIACRPapers_Good_Empty(t *testing.T) {
|
func TestExtractIACRPapers_Good_Empty_Good(t *testing.T) {
|
||||||
doc, err := html.Parse(strings.NewReader(`<html><body></body></html>`))
|
doc, err := html.Parse(strings.NewReader(`<html><body></body></html>`))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
|
@ -305,7 +305,7 @@ func TestExtractIACRPapers_Good_Empty(t *testing.T) {
|
||||||
assert.Empty(t, papers)
|
assert.Empty(t, papers)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExtractIACRPapers_Good_NoTitle(t *testing.T) {
|
func TestExtractIACRPapers_Good_NoTitle_Good(t *testing.T) {
|
||||||
doc, err := html.Parse(strings.NewReader(`<html><body><div class="paperentry"></div></body></html>`))
|
doc, err := html.Parse(strings.NewReader(`<html><body><div class="paperentry"></div></body></html>`))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,17 +15,17 @@ func TestPapersCollector_Name_Good(t *testing.T) {
|
||||||
assert.Equal(t, "papers:iacr", p.Name())
|
assert.Equal(t, "papers:iacr", p.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPapersCollector_Name_Good_ArXiv(t *testing.T) {
|
func TestPapersCollector_Name_Good_ArXiv_Good(t *testing.T) {
|
||||||
p := &PapersCollector{Source: PaperSourceArXiv}
|
p := &PapersCollector{Source: PaperSourceArXiv}
|
||||||
assert.Equal(t, "papers:arxiv", p.Name())
|
assert.Equal(t, "papers:arxiv", p.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPapersCollector_Name_Good_All(t *testing.T) {
|
func TestPapersCollector_Name_Good_All_Good(t *testing.T) {
|
||||||
p := &PapersCollector{Source: PaperSourceAll}
|
p := &PapersCollector{Source: PaperSourceAll}
|
||||||
assert.Equal(t, "papers:all", p.Name())
|
assert.Equal(t, "papers:all", p.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPapersCollector_Collect_Bad_NoQuery(t *testing.T) {
|
func TestPapersCollector_Collect_Bad_NoQuery_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
|
|
||||||
|
|
@ -34,7 +34,7 @@ func TestPapersCollector_Collect_Bad_NoQuery(t *testing.T) {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPapersCollector_Collect_Bad_UnknownSource(t *testing.T) {
|
func TestPapersCollector_Collect_Bad_UnknownSource_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
|
|
||||||
|
|
@ -43,7 +43,7 @@ func TestPapersCollector_Collect_Bad_UnknownSource(t *testing.T) {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPapersCollector_Collect_Good_DryRun(t *testing.T) {
|
func TestPapersCollector_Collect_Good_DryRun_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
cfg.DryRun = true
|
cfg.DryRun = true
|
||||||
|
|
@ -74,7 +74,7 @@ func TestFormatPaperMarkdown_Good(t *testing.T) {
|
||||||
assert.Contains(t, md, "zero-knowledge proofs")
|
assert.Contains(t, md, "zero-knowledge proofs")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFormatPaperMarkdown_Good_Minimal(t *testing.T) {
|
func TestFormatPaperMarkdown_Good_Minimal_Good(t *testing.T) {
|
||||||
md := FormatPaperMarkdown("Title Only", nil, "", "", "", "")
|
md := FormatPaperMarkdown("Title Only", nil, "", "", "", "")
|
||||||
|
|
||||||
assert.Contains(t, md, "# Title Only")
|
assert.Contains(t, md, "# Title Only")
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHTMLToMarkdown_Good_OrderedList(t *testing.T) {
|
func TestHTMLToMarkdown_Good_OrderedList_Good(t *testing.T) {
|
||||||
input := `<ol><li>First</li><li>Second</li><li>Third</li></ol>`
|
input := `<ol><li>First</li><li>Second</li><li>Third</li></ol>`
|
||||||
result, err := HTMLToMarkdown(input)
|
result, err := HTMLToMarkdown(input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -20,7 +20,7 @@ func TestHTMLToMarkdown_Good_OrderedList(t *testing.T) {
|
||||||
assert.Contains(t, result, "3. Third")
|
assert.Contains(t, result, "3. Third")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHTMLToMarkdown_Good_UnorderedList(t *testing.T) {
|
func TestHTMLToMarkdown_Good_UnorderedList_Good(t *testing.T) {
|
||||||
input := `<ul><li>Alpha</li><li>Beta</li></ul>`
|
input := `<ul><li>Alpha</li><li>Beta</li></ul>`
|
||||||
result, err := HTMLToMarkdown(input)
|
result, err := HTMLToMarkdown(input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -28,21 +28,21 @@ func TestHTMLToMarkdown_Good_UnorderedList(t *testing.T) {
|
||||||
assert.Contains(t, result, "- Beta")
|
assert.Contains(t, result, "- Beta")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHTMLToMarkdown_Good_Blockquote(t *testing.T) {
|
func TestHTMLToMarkdown_Good_Blockquote_Good(t *testing.T) {
|
||||||
input := `<blockquote>A wise quote</blockquote>`
|
input := `<blockquote>A wise quote</blockquote>`
|
||||||
result, err := HTMLToMarkdown(input)
|
result, err := HTMLToMarkdown(input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Contains(t, result, "> A wise quote")
|
assert.Contains(t, result, "> A wise quote")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHTMLToMarkdown_Good_HorizontalRule(t *testing.T) {
|
func TestHTMLToMarkdown_Good_HorizontalRule_Good(t *testing.T) {
|
||||||
input := `<p>Before</p><hr/><p>After</p>`
|
input := `<p>Before</p><hr/><p>After</p>`
|
||||||
result, err := HTMLToMarkdown(input)
|
result, err := HTMLToMarkdown(input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Contains(t, result, "---")
|
assert.Contains(t, result, "---")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHTMLToMarkdown_Good_LinkWithoutHref(t *testing.T) {
|
func TestHTMLToMarkdown_Good_LinkWithoutHref_Good(t *testing.T) {
|
||||||
input := `<a>bare link text</a>`
|
input := `<a>bare link text</a>`
|
||||||
result, err := HTMLToMarkdown(input)
|
result, err := HTMLToMarkdown(input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -50,7 +50,7 @@ func TestHTMLToMarkdown_Good_LinkWithoutHref(t *testing.T) {
|
||||||
assert.NotContains(t, result, "[")
|
assert.NotContains(t, result, "[")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHTMLToMarkdown_Good_H4H5H6(t *testing.T) {
|
func TestHTMLToMarkdown_Good_H4H5H6_Good(t *testing.T) {
|
||||||
input := `<h4>H4</h4><h5>H5</h5><h6>H6</h6>`
|
input := `<h4>H4</h4><h5>H5</h5><h6>H6</h6>`
|
||||||
result, err := HTMLToMarkdown(input)
|
result, err := HTMLToMarkdown(input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -59,7 +59,7 @@ func TestHTMLToMarkdown_Good_H4H5H6(t *testing.T) {
|
||||||
assert.Contains(t, result, "###### H6")
|
assert.Contains(t, result, "###### H6")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHTMLToMarkdown_Good_StripsStyle(t *testing.T) {
|
func TestHTMLToMarkdown_Good_StripsStyle_Good(t *testing.T) {
|
||||||
input := `<html><head><style>.foo{color:red}</style></head><body><p>Clean</p></body></html>`
|
input := `<html><head><style>.foo{color:red}</style></head><body><p>Clean</p></body></html>`
|
||||||
result, err := HTMLToMarkdown(input)
|
result, err := HTMLToMarkdown(input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -67,7 +67,7 @@ func TestHTMLToMarkdown_Good_StripsStyle(t *testing.T) {
|
||||||
assert.NotContains(t, result, "color")
|
assert.NotContains(t, result, "color")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHTMLToMarkdown_Good_LineBreak(t *testing.T) {
|
func TestHTMLToMarkdown_Good_LineBreak_Good(t *testing.T) {
|
||||||
input := `<p>Line one<br/>Line two</p>`
|
input := `<p>Line one<br/>Line two</p>`
|
||||||
result, err := HTMLToMarkdown(input)
|
result, err := HTMLToMarkdown(input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -75,7 +75,7 @@ func TestHTMLToMarkdown_Good_LineBreak(t *testing.T) {
|
||||||
assert.Contains(t, result, "Line two")
|
assert.Contains(t, result, "Line two")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHTMLToMarkdown_Good_NestedBoldItalic(t *testing.T) {
|
func TestHTMLToMarkdown_Good_NestedBoldItalic_Good(t *testing.T) {
|
||||||
input := `<b>bold text</b> and <i>italic text</i>`
|
input := `<b>bold text</b> and <i>italic text</i>`
|
||||||
result, err := HTMLToMarkdown(input)
|
result, err := HTMLToMarkdown(input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -83,7 +83,7 @@ func TestHTMLToMarkdown_Good_NestedBoldItalic(t *testing.T) {
|
||||||
assert.Contains(t, result, "*italic text*")
|
assert.Contains(t, result, "*italic text*")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJSONToMarkdown_Good_NestedObject(t *testing.T) {
|
func TestJSONToMarkdown_Good_NestedObject_Good(t *testing.T) {
|
||||||
input := `{"outer": {"inner_key": "inner_value"}}`
|
input := `{"outer": {"inner_key": "inner_value"}}`
|
||||||
result, err := JSONToMarkdown(input)
|
result, err := JSONToMarkdown(input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -91,7 +91,7 @@ func TestJSONToMarkdown_Good_NestedObject(t *testing.T) {
|
||||||
assert.Contains(t, result, "**inner_key:** inner_value")
|
assert.Contains(t, result, "**inner_key:** inner_value")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJSONToMarkdown_Good_NestedArray(t *testing.T) {
|
func TestJSONToMarkdown_Good_NestedArray_Good(t *testing.T) {
|
||||||
input := `[["a", "b"], ["c"]]`
|
input := `[["a", "b"], ["c"]]`
|
||||||
result, err := JSONToMarkdown(input)
|
result, err := JSONToMarkdown(input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -100,14 +100,14 @@ func TestJSONToMarkdown_Good_NestedArray(t *testing.T) {
|
||||||
assert.Contains(t, result, "b")
|
assert.Contains(t, result, "b")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJSONToMarkdown_Good_ScalarValue(t *testing.T) {
|
func TestJSONToMarkdown_Good_ScalarValue_Good(t *testing.T) {
|
||||||
input := `42`
|
input := `42`
|
||||||
result, err := JSONToMarkdown(input)
|
result, err := JSONToMarkdown(input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Contains(t, result, "42")
|
assert.Contains(t, result, "42")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJSONToMarkdown_Good_ArrayOfObjects(t *testing.T) {
|
func TestJSONToMarkdown_Good_ArrayOfObjects_Good(t *testing.T) {
|
||||||
input := `[{"name": "Alice"}, {"name": "Bob"}]`
|
input := `[{"name": "Alice"}, {"name": "Bob"}]`
|
||||||
result, err := JSONToMarkdown(input)
|
result, err := JSONToMarkdown(input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -117,7 +117,7 @@ func TestJSONToMarkdown_Good_ArrayOfObjects(t *testing.T) {
|
||||||
assert.Contains(t, result, "Bob")
|
assert.Contains(t, result, "Bob")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProcessor_Process_Good_CancelledContext(t *testing.T) {
|
func TestProcessor_Process_Good_CancelledContext_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
m.Dirs["/input"] = true
|
m.Dirs["/input"] = true
|
||||||
m.Files["/input/file.html"] = `<h1>Test</h1>`
|
m.Files["/input/file.html"] = `<h1>Test</h1>`
|
||||||
|
|
@ -133,7 +133,7 @@ func TestProcessor_Process_Good_CancelledContext(t *testing.T) {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProcessor_Process_Good_EmitsEvents(t *testing.T) {
|
func TestProcessor_Process_Good_EmitsEvents_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
m.Dirs["/input"] = true
|
m.Dirs["/input"] = true
|
||||||
m.Files["/input/a.html"] = `<h1>Title</h1>`
|
m.Files["/input/a.html"] = `<h1>Title</h1>`
|
||||||
|
|
@ -157,7 +157,7 @@ func TestProcessor_Process_Good_EmitsEvents(t *testing.T) {
|
||||||
assert.Equal(t, 1, completes)
|
assert.Equal(t, 1, completes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProcessor_Process_Good_BadHTML(t *testing.T) {
|
func TestProcessor_Process_Good_BadHTML_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
m.Dirs["/input"] = true
|
m.Dirs["/input"] = true
|
||||||
// html.Parse is very tolerant, so even bad HTML will parse. But we test
|
// html.Parse is very tolerant, so even bad HTML will parse. But we test
|
||||||
|
|
@ -174,7 +174,7 @@ func TestProcessor_Process_Good_BadHTML(t *testing.T) {
|
||||||
assert.Equal(t, 1, result.Items)
|
assert.Equal(t, 1, result.Items)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProcessor_Process_Good_BadJSON(t *testing.T) {
|
func TestProcessor_Process_Good_BadJSON_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
m.Dirs["/input"] = true
|
m.Dirs["/input"] = true
|
||||||
m.Files["/input/bad.json"] = `not valid json`
|
m.Files["/input/bad.json"] = `not valid json`
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ func TestProcessor_Name_Good(t *testing.T) {
|
||||||
assert.Equal(t, "process:github", p.Name())
|
assert.Equal(t, "process:github", p.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProcessor_Process_Bad_NoDir(t *testing.T) {
|
func TestProcessor_Process_Bad_NoDir_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
|
|
||||||
|
|
@ -24,7 +24,7 @@ func TestProcessor_Process_Bad_NoDir(t *testing.T) {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProcessor_Process_Good_DryRun(t *testing.T) {
|
func TestProcessor_Process_Good_DryRun_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
cfg := NewConfigWithMedium(m, "/output")
|
cfg := NewConfigWithMedium(m, "/output")
|
||||||
cfg.DryRun = true
|
cfg.DryRun = true
|
||||||
|
|
@ -36,7 +36,7 @@ func TestProcessor_Process_Good_DryRun(t *testing.T) {
|
||||||
assert.Equal(t, 0, result.Items)
|
assert.Equal(t, 0, result.Items)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProcessor_Process_Good_HTMLFiles(t *testing.T) {
|
func TestProcessor_Process_Good_HTMLFiles_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
m.Dirs["/input"] = true
|
m.Dirs["/input"] = true
|
||||||
m.Files["/input/page.html"] = `<html><body><h1>Hello</h1><p>World</p></body></html>`
|
m.Files["/input/page.html"] = `<html><body><h1>Hello</h1><p>World</p></body></html>`
|
||||||
|
|
@ -57,7 +57,7 @@ func TestProcessor_Process_Good_HTMLFiles(t *testing.T) {
|
||||||
assert.Contains(t, content, "World")
|
assert.Contains(t, content, "World")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProcessor_Process_Good_JSONFiles(t *testing.T) {
|
func TestProcessor_Process_Good_JSONFiles_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
m.Dirs["/input"] = true
|
m.Dirs["/input"] = true
|
||||||
m.Files["/input/data.json"] = `{"name": "Bitcoin", "price": 42000}`
|
m.Files["/input/data.json"] = `{"name": "Bitcoin", "price": 42000}`
|
||||||
|
|
@ -77,7 +77,7 @@ func TestProcessor_Process_Good_JSONFiles(t *testing.T) {
|
||||||
assert.Contains(t, content, "Bitcoin")
|
assert.Contains(t, content, "Bitcoin")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProcessor_Process_Good_MarkdownPassthrough(t *testing.T) {
|
func TestProcessor_Process_Good_MarkdownPassthrough_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
m.Dirs["/input"] = true
|
m.Dirs["/input"] = true
|
||||||
m.Files["/input/readme.md"] = "# Already Markdown\n\nThis is already formatted."
|
m.Files["/input/readme.md"] = "# Already Markdown\n\nThis is already formatted."
|
||||||
|
|
@ -96,7 +96,7 @@ func TestProcessor_Process_Good_MarkdownPassthrough(t *testing.T) {
|
||||||
assert.Contains(t, content, "# Already Markdown")
|
assert.Contains(t, content, "# Already Markdown")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProcessor_Process_Good_SkipUnknownTypes(t *testing.T) {
|
func TestProcessor_Process_Good_SkipUnknownTypes_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
m.Dirs["/input"] = true
|
m.Dirs["/input"] = true
|
||||||
m.Files["/input/image.png"] = "binary data"
|
m.Files["/input/image.png"] = "binary data"
|
||||||
|
|
@ -172,7 +172,7 @@ func TestHTMLToMarkdown_Good(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHTMLToMarkdown_Good_StripsScripts(t *testing.T) {
|
func TestHTMLToMarkdown_Good_StripsScripts_Good(t *testing.T) {
|
||||||
input := `<html><head><script>alert('xss')</script></head><body><p>Clean</p></body></html>`
|
input := `<html><head><script>alert('xss')</script></head><body><p>Clean</p></body></html>`
|
||||||
result, err := HTMLToMarkdown(input)
|
result, err := HTMLToMarkdown(input)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
@ -190,14 +190,14 @@ func TestJSONToMarkdown_Good(t *testing.T) {
|
||||||
assert.Contains(t, result, "42")
|
assert.Contains(t, result, "42")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJSONToMarkdown_Good_Array(t *testing.T) {
|
func TestJSONToMarkdown_Good_Array_Good(t *testing.T) {
|
||||||
input := `[{"id": 1}, {"id": 2}]`
|
input := `[{"id": 1}, {"id": 2}]`
|
||||||
result, err := JSONToMarkdown(input)
|
result, err := JSONToMarkdown(input)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Contains(t, result, "# Data")
|
assert.Contains(t, result, "# Data")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJSONToMarkdown_Bad_InvalidJSON(t *testing.T) {
|
func TestJSONToMarkdown_Bad_InvalidJSON_Good(t *testing.T) {
|
||||||
_, err := JSONToMarkdown("not json")
|
_, err := JSONToMarkdown("not json")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ func NewRateLimiter() *RateLimiter {
|
||||||
|
|
||||||
// Wait blocks until the rate limit allows the next request for the given source.
|
// Wait blocks until the rate limit allows the next request for the given source.
|
||||||
// It respects context cancellation.
|
// It respects context cancellation.
|
||||||
|
// Usage: Wait(...)
|
||||||
func (r *RateLimiter) Wait(ctx context.Context, source string) error {
|
func (r *RateLimiter) Wait(ctx context.Context, source string) error {
|
||||||
r.mu.Lock()
|
r.mu.Lock()
|
||||||
delay, ok := r.delays[source]
|
delay, ok := r.delays[source]
|
||||||
|
|
@ -77,6 +78,7 @@ func (r *RateLimiter) Wait(ctx context.Context, source string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDelay sets the delay for a source.
|
// SetDelay sets the delay for a source.
|
||||||
|
// Usage: SetDelay(...)
|
||||||
func (r *RateLimiter) SetDelay(source string, d time.Duration) {
|
func (r *RateLimiter) SetDelay(source string, d time.Duration) {
|
||||||
r.mu.Lock()
|
r.mu.Lock()
|
||||||
defer r.mu.Unlock()
|
defer r.mu.Unlock()
|
||||||
|
|
@ -84,6 +86,7 @@ func (r *RateLimiter) SetDelay(source string, d time.Duration) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDelay returns the delay configured for a source.
|
// GetDelay returns the delay configured for a source.
|
||||||
|
// Usage: GetDelay(...)
|
||||||
func (r *RateLimiter) GetDelay(source string) time.Duration {
|
func (r *RateLimiter) GetDelay(source string) time.Duration {
|
||||||
r.mu.Lock()
|
r.mu.Lock()
|
||||||
defer r.mu.Unlock()
|
defer r.mu.Unlock()
|
||||||
|
|
@ -97,6 +100,7 @@ func (r *RateLimiter) GetDelay(source string) time.Duration {
|
||||||
// Returns used and limit counts. Auto-pauses at 75% usage by increasing
|
// Returns used and limit counts. Auto-pauses at 75% usage by increasing
|
||||||
// the GitHub rate limit delay.
|
// the GitHub rate limit delay.
|
||||||
// Deprecated: Use CheckGitHubRateLimitCtx for context-aware cancellation.
|
// Deprecated: Use CheckGitHubRateLimitCtx for context-aware cancellation.
|
||||||
|
// Usage: CheckGitHubRateLimit(...)
|
||||||
func (r *RateLimiter) CheckGitHubRateLimit() (used, limit int, err error) {
|
func (r *RateLimiter) CheckGitHubRateLimit() (used, limit int, err error) {
|
||||||
return r.CheckGitHubRateLimitCtx(context.Background())
|
return r.CheckGitHubRateLimitCtx(context.Background())
|
||||||
}
|
}
|
||||||
|
|
@ -104,6 +108,7 @@ func (r *RateLimiter) CheckGitHubRateLimit() (used, limit int, err error) {
|
||||||
// CheckGitHubRateLimitCtx checks GitHub API rate limit status via gh api with context support.
|
// CheckGitHubRateLimitCtx checks GitHub API rate limit status via gh api with context support.
|
||||||
// Returns used and limit counts. Auto-pauses at 75% usage by increasing
|
// Returns used and limit counts. Auto-pauses at 75% usage by increasing
|
||||||
// the GitHub rate limit delay.
|
// the GitHub rate limit delay.
|
||||||
|
// Usage: CheckGitHubRateLimitCtx(...)
|
||||||
func (r *RateLimiter) CheckGitHubRateLimitCtx(ctx context.Context) (used, limit int, err error) {
|
func (r *RateLimiter) CheckGitHubRateLimitCtx(ctx context.Context) (used, limit int, err error) {
|
||||||
cmd := exec.CommandContext(ctx, "gh", "api", "rate_limit", "--jq", ".rate | \"\\(.used) \\(.limit)\"")
|
cmd := exec.CommandContext(ctx, "gh", "api", "rate_limit", "--jq", ".rate | \"\\(.used) \\(.limit)\"")
|
||||||
out, err := cmd.Output()
|
out, err := cmd.Output()
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ func TestRateLimiter_Wait_Good(t *testing.T) {
|
||||||
assert.GreaterOrEqual(t, time.Since(start), 40*time.Millisecond) // allow small timing variance
|
assert.GreaterOrEqual(t, time.Since(start), 40*time.Millisecond) // allow small timing variance
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRateLimiter_Wait_Bad_ContextCancelled(t *testing.T) {
|
func TestRateLimiter_Wait_Bad_ContextCancelled_Good(t *testing.T) {
|
||||||
rl := NewRateLimiter()
|
rl := NewRateLimiter()
|
||||||
rl.SetDelay("test", 5*time.Second)
|
rl.SetDelay("test", 5*time.Second)
|
||||||
|
|
||||||
|
|
@ -53,7 +53,7 @@ func TestRateLimiter_SetDelay_Good(t *testing.T) {
|
||||||
assert.Equal(t, 3*time.Second, rl.GetDelay("custom"))
|
assert.Equal(t, 3*time.Second, rl.GetDelay("custom"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRateLimiter_GetDelay_Good_Defaults(t *testing.T) {
|
func TestRateLimiter_GetDelay_Good_Defaults_Good(t *testing.T) {
|
||||||
rl := NewRateLimiter()
|
rl := NewRateLimiter()
|
||||||
|
|
||||||
assert.Equal(t, 500*time.Millisecond, rl.GetDelay("github"))
|
assert.Equal(t, 500*time.Millisecond, rl.GetDelay("github"))
|
||||||
|
|
@ -62,13 +62,13 @@ func TestRateLimiter_GetDelay_Good_Defaults(t *testing.T) {
|
||||||
assert.Equal(t, 1*time.Second, rl.GetDelay("iacr"))
|
assert.Equal(t, 1*time.Second, rl.GetDelay("iacr"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRateLimiter_GetDelay_Good_UnknownSource(t *testing.T) {
|
func TestRateLimiter_GetDelay_Good_UnknownSource_Good(t *testing.T) {
|
||||||
rl := NewRateLimiter()
|
rl := NewRateLimiter()
|
||||||
// Unknown sources should get the default 500ms delay
|
// Unknown sources should get the default 500ms delay
|
||||||
assert.Equal(t, 500*time.Millisecond, rl.GetDelay("unknown"))
|
assert.Equal(t, 500*time.Millisecond, rl.GetDelay("unknown"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRateLimiter_Wait_Good_UnknownSource(t *testing.T) {
|
func TestRateLimiter_Wait_Good_UnknownSource_Good(t *testing.T) {
|
||||||
rl := NewRateLimiter()
|
rl := NewRateLimiter()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestState_Get_Good_ReturnsCopy(t *testing.T) {
|
func TestState_Get_Good_ReturnsCopy_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
s := NewState(m, "/state.json")
|
s := NewState(m, "/state.json")
|
||||||
|
|
||||||
|
|
@ -26,7 +26,7 @@ func TestState_Get_Good_ReturnsCopy(t *testing.T) {
|
||||||
assert.Equal(t, 5, again.Items, "internal state should not be mutated")
|
assert.Equal(t, 5, again.Items, "internal state should not be mutated")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestState_Save_Good_WritesJSON(t *testing.T) {
|
func TestState_Save_Good_WritesJSON_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
s := NewState(m, "/data/state.json")
|
s := NewState(m, "/data/state.json")
|
||||||
|
|
||||||
|
|
@ -42,7 +42,7 @@ func TestState_Save_Good_WritesJSON(t *testing.T) {
|
||||||
assert.Contains(t, content, `"abc"`)
|
assert.Contains(t, content, `"abc"`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestState_Load_Good_NullJSON(t *testing.T) {
|
func TestState_Load_Good_NullJSON_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
m.Files["/state.json"] = "null"
|
m.Files["/state.json"] = "null"
|
||||||
|
|
||||||
|
|
@ -55,7 +55,7 @@ func TestState_Load_Good_NullJSON(t *testing.T) {
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestState_SaveLoad_Good_WithCursor(t *testing.T) {
|
func TestState_SaveLoad_Good_WithCursor_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
s := NewState(m, "/state.json")
|
s := NewState(m, "/state.json")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ func TestState_SaveLoad_Good(t *testing.T) {
|
||||||
assert.True(t, now.Equal(got.LastRun))
|
assert.True(t, now.Equal(got.LastRun))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestState_Load_Good_NoFile(t *testing.T) {
|
func TestState_Load_Good_NoFile_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
s := NewState(m, "/nonexistent.json")
|
s := NewState(m, "/nonexistent.json")
|
||||||
|
|
||||||
|
|
@ -88,7 +88,7 @@ func TestState_Load_Good_NoFile(t *testing.T) {
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestState_Load_Bad_InvalidJSON(t *testing.T) {
|
func TestState_Load_Bad_InvalidJSON_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
m.Files["/state.json"] = "not valid json"
|
m.Files["/state.json"] = "not valid json"
|
||||||
|
|
||||||
|
|
@ -97,7 +97,7 @@ func TestState_Load_Bad_InvalidJSON(t *testing.T) {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestState_SaveLoad_Good_MultipleEntries(t *testing.T) {
|
func TestState_SaveLoad_Good_MultipleEntries_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
s := NewState(m, "/state.json")
|
s := NewState(m, "/state.json")
|
||||||
|
|
||||||
|
|
@ -125,7 +125,7 @@ func TestState_SaveLoad_Good_MultipleEntries(t *testing.T) {
|
||||||
assert.Equal(t, 30, c.Items)
|
assert.Equal(t, 30, c.Items)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestState_Set_Good_Overwrite(t *testing.T) {
|
func TestState_Set_Good_Overwrite_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
s := NewState(m, "/state.json")
|
s := NewState(m, "/state.json")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,15 +34,19 @@ func New(url, token string) (*Client, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// API exposes the underlying SDK client for direct access.
|
// API exposes the underlying SDK client for direct access.
|
||||||
|
// Usage: API(...)
|
||||||
func (c *Client) API() *forgejo.Client { return c.api }
|
func (c *Client) API() *forgejo.Client { return c.api }
|
||||||
|
|
||||||
// URL returns the Forgejo instance URL.
|
// URL returns the Forgejo instance URL.
|
||||||
|
// Usage: URL(...)
|
||||||
func (c *Client) URL() string { return c.url }
|
func (c *Client) URL() string { return c.url }
|
||||||
|
|
||||||
// Token returns the Forgejo API token.
|
// Token returns the Forgejo API token.
|
||||||
|
// Usage: Token(...)
|
||||||
func (c *Client) Token() string { return c.token }
|
func (c *Client) Token() string { return c.token }
|
||||||
|
|
||||||
// GetCurrentUser returns the authenticated user's information.
|
// GetCurrentUser returns the authenticated user's information.
|
||||||
|
// Usage: GetCurrentUser(...)
|
||||||
func (c *Client) GetCurrentUser() (*forgejo.User, error) {
|
func (c *Client) GetCurrentUser() (*forgejo.User, error) {
|
||||||
user, _, err := c.api.GetMyUserInfo()
|
user, _, err := c.api.GetMyUserInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -52,6 +56,7 @@ func (c *Client) GetCurrentUser() (*forgejo.User, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForkRepo forks a repository. If org is non-empty, forks into that organisation.
|
// ForkRepo forks a repository. If org is non-empty, forks into that organisation.
|
||||||
|
// Usage: ForkRepo(...)
|
||||||
func (c *Client) ForkRepo(owner, repo string, org string) (*forgejo.Repository, error) {
|
func (c *Client) ForkRepo(owner, repo string, org string) (*forgejo.Repository, error) {
|
||||||
opts := forgejo.CreateForkOption{}
|
opts := forgejo.CreateForkOption{}
|
||||||
if org != "" {
|
if org != "" {
|
||||||
|
|
@ -66,6 +71,7 @@ func (c *Client) ForkRepo(owner, repo string, org string) (*forgejo.Repository,
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreatePullRequest creates a pull request on the given repository.
|
// CreatePullRequest creates a pull request on the given repository.
|
||||||
|
// Usage: CreatePullRequest(...)
|
||||||
func (c *Client) CreatePullRequest(owner, repo string, opts forgejo.CreatePullRequestOption) (*forgejo.PullRequest, error) {
|
func (c *Client) CreatePullRequest(owner, repo string, opts forgejo.CreatePullRequestOption) (*forgejo.PullRequest, error) {
|
||||||
pr, _, err := c.api.CreatePullRequest(owner, repo, opts)
|
pr, _, err := c.api.CreatePullRequest(owner, repo, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ func TestNew_Good(t *testing.T) {
|
||||||
assert.Equal(t, "test-token-123", client.Token())
|
assert.Equal(t, "test-token-123", client.Token())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNew_Bad_InvalidURL(t *testing.T) {
|
func TestNew_Bad_InvalidURL_Good(t *testing.T) {
|
||||||
// The Forgejo SDK may reject certain URL formats.
|
// The Forgejo SDK may reject certain URL formats.
|
||||||
_, err := New("://invalid-url", "token")
|
_, err := New("://invalid-url", "token")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
|
|
@ -63,7 +63,7 @@ func TestClient_GetCurrentUser_Good(t *testing.T) {
|
||||||
assert.Equal(t, "test-user", user.UserName)
|
assert.Equal(t, "test-user", user.UserName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_GetCurrentUser_Bad_ServerError(t *testing.T) {
|
func TestClient_GetCurrentUser_Bad_ServerError_Good(t *testing.T) {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("/api/v1/version", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/api/v1/version", func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
@ -91,7 +91,7 @@ func TestClient_SetPRDraft_Good(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_SetPRDraft_Good_Undraft(t *testing.T) {
|
func TestClient_SetPRDraft_Good_Undraft_Good(t *testing.T) {
|
||||||
client, srv := newTestClient(t)
|
client, srv := newTestClient(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -99,7 +99,7 @@ func TestClient_SetPRDraft_Good_Undraft(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_SetPRDraft_Bad_ServerError(t *testing.T) {
|
func TestClient_SetPRDraft_Bad_ServerError_Good(t *testing.T) {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("/api/v1/version", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/api/v1/version", func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
@ -123,7 +123,7 @@ func TestClient_SetPRDraft_Bad_ServerError(t *testing.T) {
|
||||||
assert.Contains(t, err.Error(), "unexpected status 403")
|
assert.Contains(t, err.Error(), "unexpected status 403")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_SetPRDraft_Bad_ConnectionRefused(t *testing.T) {
|
func TestClient_SetPRDraft_Bad_ConnectionRefused_Good(t *testing.T) {
|
||||||
// Use a closed server to simulate connection errors.
|
// Use a closed server to simulate connection errors.
|
||||||
srv := newMockForgejoServer(t)
|
srv := newMockForgejoServer(t)
|
||||||
client, err := New(srv.URL, "token")
|
client, err := New(srv.URL, "token")
|
||||||
|
|
@ -134,7 +134,7 @@ func TestClient_SetPRDraft_Bad_ConnectionRefused(t *testing.T) {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_SetPRDraft_Good_URLConstruction(t *testing.T) {
|
func TestClient_SetPRDraft_Good_URLConstruction_Good(t *testing.T) {
|
||||||
// Verify the URL is constructed correctly by checking the request path.
|
// Verify the URL is constructed correctly by checking the request path.
|
||||||
var capturedPath string
|
var capturedPath string
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
|
|
@ -158,7 +158,7 @@ func TestClient_SetPRDraft_Good_URLConstruction(t *testing.T) {
|
||||||
assert.Equal(t, "/api/v1/repos/my-org/my-repo/pulls/42", capturedPath)
|
assert.Equal(t, "/api/v1/repos/my-org/my-repo/pulls/42", capturedPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_SetPRDraft_Good_AuthHeader(t *testing.T) {
|
func TestClient_SetPRDraft_Good_AuthHeader_Good(t *testing.T) {
|
||||||
// Verify the authorisation header is set correctly.
|
// Verify the authorisation header is set correctly.
|
||||||
var capturedAuth string
|
var capturedAuth string
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
|
|
@ -184,7 +184,7 @@ func TestClient_SetPRDraft_Good_AuthHeader(t *testing.T) {
|
||||||
|
|
||||||
// --- PRMeta and Comment struct tests ---
|
// --- PRMeta and Comment struct tests ---
|
||||||
|
|
||||||
func TestPRMeta_Good_Fields(t *testing.T) {
|
func TestPRMeta_Good_Fields_Good(t *testing.T) {
|
||||||
meta := &PRMeta{
|
meta := &PRMeta{
|
||||||
Number: 42,
|
Number: 42,
|
||||||
Title: "Test PR",
|
Title: "Test PR",
|
||||||
|
|
@ -210,7 +210,7 @@ func TestPRMeta_Good_Fields(t *testing.T) {
|
||||||
assert.Equal(t, 5, meta.CommentCount)
|
assert.Equal(t, 5, meta.CommentCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestComment_Good_Fields(t *testing.T) {
|
func TestComment_Good_Fields_Good(t *testing.T) {
|
||||||
comment := Comment{
|
comment := Comment{
|
||||||
ID: 123,
|
ID: 123,
|
||||||
Author: "reviewer",
|
Author: "reviewer",
|
||||||
|
|
@ -224,7 +224,7 @@ func TestComment_Good_Fields(t *testing.T) {
|
||||||
|
|
||||||
// --- MergePullRequest merge style mapping ---
|
// --- MergePullRequest merge style mapping ---
|
||||||
|
|
||||||
func TestMergePullRequest_Good_StyleMapping(t *testing.T) {
|
func TestMergePullRequest_Good_StyleMapping_Good(t *testing.T) {
|
||||||
// We can't easily test the SDK call, but we can verify the method
|
// We can't easily test the SDK call, but we can verify the method
|
||||||
// errors when the server returns failure. This exercises the style mapping code.
|
// errors when the server returns failure. This exercises the style mapping code.
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
|
@ -262,7 +262,7 @@ func TestMergePullRequest_Good_StyleMapping(t *testing.T) {
|
||||||
|
|
||||||
// --- ListIssuesOpts defaulting ---
|
// --- ListIssuesOpts defaulting ---
|
||||||
|
|
||||||
func TestListIssuesOpts_Good_Defaults(t *testing.T) {
|
func TestListIssuesOpts_Good_Defaults_Good(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
opts ListIssuesOpts
|
opts ListIssuesOpts
|
||||||
|
|
@ -325,7 +325,7 @@ func TestListIssuesOpts_Good_Defaults(t *testing.T) {
|
||||||
|
|
||||||
// --- ForkRepo error handling ---
|
// --- ForkRepo error handling ---
|
||||||
|
|
||||||
func TestClient_ForkRepo_Good_WithOrg(t *testing.T) {
|
func TestClient_ForkRepo_Good_WithOrg_Good(t *testing.T) {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("/api/v1/version", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/api/v1/version", func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
@ -355,7 +355,7 @@ func TestClient_ForkRepo_Good_WithOrg(t *testing.T) {
|
||||||
assert.Equal(t, "target-org", capturedBody["organization"])
|
assert.Equal(t, "target-org", capturedBody["organization"])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_ForkRepo_Good_WithoutOrg(t *testing.T) {
|
func TestClient_ForkRepo_Good_WithoutOrg_Good(t *testing.T) {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("/api/v1/version", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/api/v1/version", func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
@ -386,7 +386,7 @@ func TestClient_ForkRepo_Good_WithoutOrg(t *testing.T) {
|
||||||
// The SDK may or may not include it in the JSON; just verify the fork succeeded.
|
// The SDK may or may not include it in the JSON; just verify the fork succeeded.
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_ForkRepo_Bad_ServerError(t *testing.T) {
|
func TestClient_ForkRepo_Bad_ServerError_Good(t *testing.T) {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("/api/v1/version", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/api/v1/version", func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
@ -408,7 +408,7 @@ func TestClient_ForkRepo_Bad_ServerError(t *testing.T) {
|
||||||
|
|
||||||
// --- CreatePullRequest error handling ---
|
// --- CreatePullRequest error handling ---
|
||||||
|
|
||||||
func TestClient_CreatePullRequest_Bad_ServerError(t *testing.T) {
|
func TestClient_CreatePullRequest_Bad_ServerError_Good(t *testing.T) {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("/api/v1/version", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/api/v1/version", func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
@ -440,7 +440,7 @@ func TestCommentPageSize_Good(t *testing.T) {
|
||||||
|
|
||||||
// --- ListPullRequests state mapping ---
|
// --- ListPullRequests state mapping ---
|
||||||
|
|
||||||
func TestListPullRequests_Good_StateMapping(t *testing.T) {
|
func TestListPullRequests_Good_StateMapping_Good(t *testing.T) {
|
||||||
// Verify state mapping via error path (server returns error).
|
// Verify state mapping via error path (server returns error).
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ func isolateConfigEnv(t *testing.T) {
|
||||||
t.Setenv("HOME", t.TempDir())
|
t.Setenv("HOME", t.TempDir())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveConfig_Good_Defaults(t *testing.T) {
|
func TestResolveConfig_Good_Defaults_Good(t *testing.T) {
|
||||||
isolateConfigEnv(t)
|
isolateConfigEnv(t)
|
||||||
|
|
||||||
url, token, err := ResolveConfig("", "")
|
url, token, err := ResolveConfig("", "")
|
||||||
|
|
@ -27,7 +27,7 @@ func TestResolveConfig_Good_Defaults(t *testing.T) {
|
||||||
assert.Empty(t, token, "token should be empty when nothing configured")
|
assert.Empty(t, token, "token should be empty when nothing configured")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveConfig_Good_FlagsOverrideAll(t *testing.T) {
|
func TestResolveConfig_Good_FlagsOverrideAll_Good(t *testing.T) {
|
||||||
isolateConfigEnv(t)
|
isolateConfigEnv(t)
|
||||||
t.Setenv("FORGE_URL", "https://env-url.example.com")
|
t.Setenv("FORGE_URL", "https://env-url.example.com")
|
||||||
t.Setenv("FORGE_TOKEN", "env-token-abc")
|
t.Setenv("FORGE_TOKEN", "env-token-abc")
|
||||||
|
|
@ -38,7 +38,7 @@ func TestResolveConfig_Good_FlagsOverrideAll(t *testing.T) {
|
||||||
assert.Equal(t, "flag-token-xyz", token, "flag token should override env")
|
assert.Equal(t, "flag-token-xyz", token, "flag token should override env")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveConfig_Good_EnvVarsOverrideConfig(t *testing.T) {
|
func TestResolveConfig_Good_EnvVarsOverrideConfig_Good(t *testing.T) {
|
||||||
isolateConfigEnv(t)
|
isolateConfigEnv(t)
|
||||||
t.Setenv("FORGE_URL", "https://env-url.example.com")
|
t.Setenv("FORGE_URL", "https://env-url.example.com")
|
||||||
t.Setenv("FORGE_TOKEN", "env-token-123")
|
t.Setenv("FORGE_TOKEN", "env-token-123")
|
||||||
|
|
@ -49,7 +49,7 @@ func TestResolveConfig_Good_EnvVarsOverrideConfig(t *testing.T) {
|
||||||
assert.Equal(t, "env-token-123", token)
|
assert.Equal(t, "env-token-123", token)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveConfig_Good_PartialOverrides(t *testing.T) {
|
func TestResolveConfig_Good_PartialOverrides_Good(t *testing.T) {
|
||||||
isolateConfigEnv(t)
|
isolateConfigEnv(t)
|
||||||
// Set only env URL, flag token.
|
// Set only env URL, flag token.
|
||||||
t.Setenv("FORGE_URL", "https://env-only.example.com")
|
t.Setenv("FORGE_URL", "https://env-only.example.com")
|
||||||
|
|
@ -60,7 +60,7 @@ func TestResolveConfig_Good_PartialOverrides(t *testing.T) {
|
||||||
assert.Equal(t, "flag-only-token", token, "flag token should be used")
|
assert.Equal(t, "flag-only-token", token, "flag token should be used")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveConfig_Good_URLDefaultsWhenEmpty(t *testing.T) {
|
func TestResolveConfig_Good_URLDefaultsWhenEmpty_Good(t *testing.T) {
|
||||||
isolateConfigEnv(t)
|
isolateConfigEnv(t)
|
||||||
t.Setenv("FORGE_TOKEN", "some-token")
|
t.Setenv("FORGE_TOKEN", "some-token")
|
||||||
|
|
||||||
|
|
@ -76,7 +76,7 @@ func TestConstants_Good(t *testing.T) {
|
||||||
assert.Equal(t, "http://localhost:4000", DefaultURL)
|
assert.Equal(t, "http://localhost:4000", DefaultURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewFromConfig_Bad_NoToken(t *testing.T) {
|
func TestNewFromConfig_Bad_NoToken_Good(t *testing.T) {
|
||||||
isolateConfigEnv(t)
|
isolateConfigEnv(t)
|
||||||
|
|
||||||
_, err := NewFromConfig("", "")
|
_, err := NewFromConfig("", "")
|
||||||
|
|
@ -84,7 +84,7 @@ func TestNewFromConfig_Bad_NoToken(t *testing.T) {
|
||||||
assert.Contains(t, err.Error(), "no API token configured")
|
assert.Contains(t, err.Error(), "no API token configured")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewFromConfig_Good_WithFlagToken(t *testing.T) {
|
func TestNewFromConfig_Good_WithFlagToken_Good(t *testing.T) {
|
||||||
isolateConfigEnv(t)
|
isolateConfigEnv(t)
|
||||||
|
|
||||||
// The Forgejo SDK NewClient validates the token by calling /api/v1/version,
|
// The Forgejo SDK NewClient validates the token by calling /api/v1/version,
|
||||||
|
|
@ -99,7 +99,7 @@ func TestNewFromConfig_Good_WithFlagToken(t *testing.T) {
|
||||||
assert.Equal(t, "test-token", client.Token())
|
assert.Equal(t, "test-token", client.Token())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewFromConfig_Good_EnvToken(t *testing.T) {
|
func TestNewFromConfig_Good_EnvToken_Good(t *testing.T) {
|
||||||
isolateConfigEnv(t)
|
isolateConfigEnv(t)
|
||||||
|
|
||||||
srv := newMockForgejoServer(t)
|
srv := newMockForgejoServer(t)
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ func TestClient_ListIssues_Good(t *testing.T) {
|
||||||
assert.Equal(t, "Issue 1", issues[0].Title)
|
assert.Equal(t, "Issue 1", issues[0].Title)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_ListIssues_Good_StateMapping(t *testing.T) {
|
func TestClient_ListIssues_Good_StateMapping_Good(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
state string
|
state string
|
||||||
|
|
@ -43,7 +43,7 @@ func TestClient_ListIssues_Good_StateMapping(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_ListIssues_Good_CustomPageAndLimit(t *testing.T) {
|
func TestClient_ListIssues_Good_CustomPageAndLimit_Good(t *testing.T) {
|
||||||
client, srv := newTestClient(t)
|
client, srv := newTestClient(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -54,7 +54,7 @@ func TestClient_ListIssues_Good_CustomPageAndLimit(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_ListIssues_Bad_ServerError(t *testing.T) {
|
func TestClient_ListIssues_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -72,7 +72,7 @@ func TestClient_GetIssue_Good(t *testing.T) {
|
||||||
assert.Equal(t, "Issue 1", issue.Title)
|
assert.Equal(t, "Issue 1", issue.Title)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_GetIssue_Bad_ServerError(t *testing.T) {
|
func TestClient_GetIssue_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -93,7 +93,7 @@ func TestClient_CreateIssue_Good(t *testing.T) {
|
||||||
assert.NotNil(t, issue)
|
assert.NotNil(t, issue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_CreateIssue_Bad_ServerError(t *testing.T) {
|
func TestClient_CreateIssue_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -115,7 +115,7 @@ func TestClient_EditIssue_Good(t *testing.T) {
|
||||||
assert.NotNil(t, issue)
|
assert.NotNil(t, issue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_EditIssue_Bad_ServerError(t *testing.T) {
|
func TestClient_EditIssue_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -134,7 +134,7 @@ func TestClient_AssignIssue_Good(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_AssignIssue_Bad_ServerError(t *testing.T) {
|
func TestClient_AssignIssue_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -153,7 +153,7 @@ func TestClient_ListPullRequests_Good(t *testing.T) {
|
||||||
assert.Equal(t, "PR 1", prs[0].Title)
|
assert.Equal(t, "PR 1", prs[0].Title)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_ListPullRequests_Good_StateMapping(t *testing.T) {
|
func TestClient_ListPullRequests_Good_StateMapping_Good(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
state string
|
state string
|
||||||
|
|
@ -175,7 +175,7 @@ func TestClient_ListPullRequests_Good_StateMapping(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_ListPullRequests_Bad_ServerError(t *testing.T) {
|
func TestClient_ListPullRequests_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -193,7 +193,7 @@ func TestClient_GetPullRequest_Good(t *testing.T) {
|
||||||
assert.Equal(t, "PR 1", pr.Title)
|
assert.Equal(t, "PR 1", pr.Title)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_GetPullRequest_Bad_ServerError(t *testing.T) {
|
func TestClient_GetPullRequest_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -210,7 +210,7 @@ func TestClient_CreateIssueComment_Good(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_CreateIssueComment_Bad_ServerError(t *testing.T) {
|
func TestClient_CreateIssueComment_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -229,7 +229,7 @@ func TestClient_ListIssueComments_Good(t *testing.T) {
|
||||||
assert.Equal(t, "comment 1", comments[0].Body)
|
assert.Equal(t, "comment 1", comments[0].Body)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_ListIssueComments_Bad_ServerError(t *testing.T) {
|
func TestClient_ListIssueComments_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -246,7 +246,7 @@ func TestClient_CloseIssue_Good(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_CloseIssue_Bad_ServerError(t *testing.T) {
|
func TestClient_CloseIssue_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ func TestClient_ListRepoLabels_Good(t *testing.T) {
|
||||||
assert.Equal(t, "feature", labels[1].Name)
|
assert.Equal(t, "feature", labels[1].Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_ListRepoLabels_Bad_ServerError(t *testing.T) {
|
func TestClient_ListRepoLabels_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -44,7 +44,7 @@ func TestClient_CreateRepoLabel_Good(t *testing.T) {
|
||||||
assert.NotNil(t, label)
|
assert.NotNil(t, label)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_CreateRepoLabel_Bad_ServerError(t *testing.T) {
|
func TestClient_CreateRepoLabel_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -62,7 +62,7 @@ func TestClient_GetLabelByName_Good(t *testing.T) {
|
||||||
assert.Equal(t, "bug", label.Name)
|
assert.Equal(t, "bug", label.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_GetLabelByName_Good_CaseInsensitive(t *testing.T) {
|
func TestClient_GetLabelByName_Good_CaseInsensitive_Good(t *testing.T) {
|
||||||
client, srv := newTestClient(t)
|
client, srv := newTestClient(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -71,7 +71,7 @@ func TestClient_GetLabelByName_Good_CaseInsensitive(t *testing.T) {
|
||||||
assert.Equal(t, "bug", label.Name)
|
assert.Equal(t, "bug", label.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_GetLabelByName_Bad_NotFound(t *testing.T) {
|
func TestClient_GetLabelByName_Bad_NotFound_Good(t *testing.T) {
|
||||||
client, srv := newTestClient(t)
|
client, srv := newTestClient(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -80,7 +80,7 @@ func TestClient_GetLabelByName_Bad_NotFound(t *testing.T) {
|
||||||
assert.Contains(t, err.Error(), "label nonexistent not found")
|
assert.Contains(t, err.Error(), "label nonexistent not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_EnsureLabel_Good_Exists(t *testing.T) {
|
func TestClient_EnsureLabel_Good_Exists_Good(t *testing.T) {
|
||||||
client, srv := newTestClient(t)
|
client, srv := newTestClient(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -90,7 +90,7 @@ func TestClient_EnsureLabel_Good_Exists(t *testing.T) {
|
||||||
assert.Equal(t, "bug", label.Name)
|
assert.Equal(t, "bug", label.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_EnsureLabel_Good_Creates(t *testing.T) {
|
func TestClient_EnsureLabel_Good_Creates_Good(t *testing.T) {
|
||||||
client, srv := newTestClient(t)
|
client, srv := newTestClient(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -110,7 +110,7 @@ func TestClient_ListOrgLabels_Good(t *testing.T) {
|
||||||
assert.NotEmpty(t, labels)
|
assert.NotEmpty(t, labels)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_ListOrgLabels_Bad_ServerError(t *testing.T) {
|
func TestClient_ListOrgLabels_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -126,7 +126,7 @@ func TestClient_AddIssueLabels_Good(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_AddIssueLabels_Bad_ServerError(t *testing.T) {
|
func TestClient_AddIssueLabels_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -143,7 +143,7 @@ func TestClient_RemoveIssueLabel_Good(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_RemoveIssueLabel_Bad_ServerError(t *testing.T) {
|
func TestClient_RemoveIssueLabel_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ const commentPageSize = 50
|
||||||
|
|
||||||
// GetPRMeta returns structural signals for a pull request.
|
// GetPRMeta returns structural signals for a pull request.
|
||||||
// This is the Forgejo side of the dual MetaReader described in the pipeline design.
|
// This is the Forgejo side of the dual MetaReader described in the pipeline design.
|
||||||
|
// Usage: GetPRMeta(...)
|
||||||
func (c *Client) GetPRMeta(owner, repo string, pr int64) (*PRMeta, error) {
|
func (c *Client) GetPRMeta(owner, repo string, pr int64) (*PRMeta, error) {
|
||||||
pull, _, err := c.api.GetPullRequest(owner, repo, pr)
|
pull, _, err := c.api.GetPullRequest(owner, repo, pr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -97,6 +98,7 @@ func (c *Client) GetPRMeta(owner, repo string, pr int64) (*PRMeta, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCommentBodies returns all comment bodies for a pull request.
|
// GetCommentBodies returns all comment bodies for a pull request.
|
||||||
|
// Usage: GetCommentBodies(...)
|
||||||
func (c *Client) GetCommentBodies(owner, repo string, pr int64) ([]Comment, error) {
|
func (c *Client) GetCommentBodies(owner, repo string, pr int64) ([]Comment, error) {
|
||||||
var comments []Comment
|
var comments []Comment
|
||||||
page := 1
|
page := 1
|
||||||
|
|
@ -136,6 +138,7 @@ func (c *Client) GetCommentBodies(owner, repo string, pr int64) ([]Comment, erro
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetIssueBody returns the body text of an issue.
|
// GetIssueBody returns the body text of an issue.
|
||||||
|
// Usage: GetIssueBody(...)
|
||||||
func (c *Client) GetIssueBody(owner, repo string, issue int64) (string, error) {
|
func (c *Client) GetIssueBody(owner, repo string, issue int64) (string, error) {
|
||||||
iss, _, err := c.api.GetIssue(owner, repo, issue)
|
iss, _, err := c.api.GetIssue(owner, repo, issue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ func TestClient_GetPRMeta_Good(t *testing.T) {
|
||||||
assert.False(t, meta.IsMerged)
|
assert.False(t, meta.IsMerged)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_GetPRMeta_Bad_ServerError(t *testing.T) {
|
func TestClient_GetPRMeta_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -47,7 +47,7 @@ func TestClient_GetCommentBodies_Good(t *testing.T) {
|
||||||
assert.Equal(t, "user2", comments[1].Author)
|
assert.Equal(t, "user2", comments[1].Author)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_GetCommentBodies_Bad_ServerError(t *testing.T) {
|
func TestClient_GetCommentBodies_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -65,7 +65,7 @@ func TestClient_GetIssueBody_Good(t *testing.T) {
|
||||||
assert.Equal(t, "First issue body", body)
|
assert.Equal(t, "First issue body", body)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_GetIssueBody_Bad_ServerError(t *testing.T) {
|
func TestClient_GetIssueBody_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ func TestClient_ListMyOrgs_Good(t *testing.T) {
|
||||||
assert.Equal(t, "test-org", orgs[0].UserName)
|
assert.Equal(t, "test-org", orgs[0].UserName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_ListMyOrgs_Bad_ServerError(t *testing.T) {
|
func TestClient_ListMyOrgs_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -39,7 +39,7 @@ func TestClient_GetOrg_Good(t *testing.T) {
|
||||||
assert.Equal(t, "test-org", org.UserName)
|
assert.Equal(t, "test-org", org.UserName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_GetOrg_Bad_ServerError(t *testing.T) {
|
func TestClient_GetOrg_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -61,7 +61,7 @@ func TestClient_CreateOrg_Good(t *testing.T) {
|
||||||
assert.NotNil(t, org)
|
assert.NotNil(t, org)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_CreateOrg_Bad_ServerError(t *testing.T) {
|
func TestClient_CreateOrg_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ func TestClient_MergePullRequest_Good(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_MergePullRequest_Good_Squash(t *testing.T) {
|
func TestClient_MergePullRequest_Good_Squash_Good(t *testing.T) {
|
||||||
client, srv := newTestClient(t)
|
client, srv := newTestClient(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -29,7 +29,7 @@ func TestClient_MergePullRequest_Good_Squash(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_MergePullRequest_Good_Rebase(t *testing.T) {
|
func TestClient_MergePullRequest_Good_Rebase_Good(t *testing.T) {
|
||||||
client, srv := newTestClient(t)
|
client, srv := newTestClient(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -37,7 +37,7 @@ func TestClient_MergePullRequest_Good_Rebase(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_MergePullRequest_Bad_ServerError(t *testing.T) {
|
func TestClient_MergePullRequest_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -60,7 +60,7 @@ func TestClient_ListPRReviews_Good(t *testing.T) {
|
||||||
require.Len(t, reviews, 1)
|
require.Len(t, reviews, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_ListPRReviews_Bad_ServerError(t *testing.T) {
|
func TestClient_ListPRReviews_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -78,7 +78,7 @@ func TestClient_GetCombinedStatus_Good(t *testing.T) {
|
||||||
assert.NotNil(t, status)
|
assert.NotNil(t, status)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_GetCombinedStatus_Bad_ServerError(t *testing.T) {
|
func TestClient_GetCombinedStatus_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -95,7 +95,7 @@ func TestClient_DismissReview_Good(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_DismissReview_Bad_ServerError(t *testing.T) {
|
func TestClient_DismissReview_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -104,7 +104,7 @@ func TestClient_DismissReview_Bad_ServerError(t *testing.T) {
|
||||||
assert.Contains(t, err.Error(), "failed to dismiss review")
|
assert.Contains(t, err.Error(), "failed to dismiss review")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_SetPRDraft_Good_Request(t *testing.T) {
|
func TestClient_SetPRDraft_Good_Request_Good(t *testing.T) {
|
||||||
var method, path string
|
var method, path string
|
||||||
var payload map[string]any
|
var payload map[string]any
|
||||||
|
|
||||||
|
|
@ -132,7 +132,7 @@ func TestClient_SetPRDraft_Good_Request(t *testing.T) {
|
||||||
assert.Equal(t, false, payload["draft"])
|
assert.Equal(t, false, payload["draft"])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_SetPRDraft_Bad_PathTraversalOwner(t *testing.T) {
|
func TestClient_SetPRDraft_Bad_PathTraversalOwner_Good(t *testing.T) {
|
||||||
client, srv := newTestClient(t)
|
client, srv := newTestClient(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -141,7 +141,7 @@ func TestClient_SetPRDraft_Bad_PathTraversalOwner(t *testing.T) {
|
||||||
assert.Contains(t, err.Error(), "invalid owner")
|
assert.Contains(t, err.Error(), "invalid owner")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_SetPRDraft_Bad_PathTraversalRepo(t *testing.T) {
|
func TestClient_SetPRDraft_Bad_PathTraversalRepo_Good(t *testing.T) {
|
||||||
client, srv := newTestClient(t)
|
client, srv := newTestClient(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ func TestClient_ListOrgRepos_Good(t *testing.T) {
|
||||||
assert.Equal(t, "org-repo", repos[0].Name)
|
assert.Equal(t, "org-repo", repos[0].Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_ListOrgRepos_Bad_ServerError(t *testing.T) {
|
func TestClient_ListOrgRepos_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -41,7 +41,7 @@ func TestClient_ListUserRepos_Good(t *testing.T) {
|
||||||
assert.Equal(t, "repo-b", repos[1].Name)
|
assert.Equal(t, "repo-b", repos[1].Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_ListUserRepos_Bad_ServerError(t *testing.T) {
|
func TestClient_ListUserRepos_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -59,7 +59,7 @@ func TestClient_GetRepo_Good(t *testing.T) {
|
||||||
assert.Equal(t, "org-repo", repo.Name)
|
assert.Equal(t, "org-repo", repo.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_GetRepo_Bad_ServerError(t *testing.T) {
|
func TestClient_GetRepo_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -80,7 +80,7 @@ func TestClient_CreateOrgRepo_Good(t *testing.T) {
|
||||||
assert.NotNil(t, repo)
|
assert.NotNil(t, repo)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_CreateOrgRepo_Bad_ServerError(t *testing.T) {
|
func TestClient_CreateOrgRepo_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -99,7 +99,7 @@ func TestClient_DeleteRepo_Good(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_DeleteRepo_Bad_ServerError(t *testing.T) {
|
func TestClient_DeleteRepo_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -121,7 +121,7 @@ func TestClient_MigrateRepo_Good(t *testing.T) {
|
||||||
assert.NotNil(t, repo)
|
assert.NotNil(t, repo)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_MigrateRepo_Bad_ServerError(t *testing.T) {
|
func TestClient_MigrateRepo_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ func TestClient_CreateRepoWebhook_Good(t *testing.T) {
|
||||||
assert.NotNil(t, hook)
|
assert.NotNil(t, hook)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_CreateRepoWebhook_Bad_ServerError(t *testing.T) {
|
func TestClient_CreateRepoWebhook_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -45,7 +45,7 @@ func TestClient_ListRepoWebhooks_Good(t *testing.T) {
|
||||||
require.Len(t, hooks, 1)
|
require.Len(t, hooks, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_ListRepoWebhooks_Bad_ServerError(t *testing.T) {
|
func TestClient_ListRepoWebhooks_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,9 @@ func New(url, token string) (*Client, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// API exposes the underlying SDK client for direct access.
|
// API exposes the underlying SDK client for direct access.
|
||||||
|
// Usage: API(...)
|
||||||
func (c *Client) API() *gitea.Client { return c.api }
|
func (c *Client) API() *gitea.Client { return c.api }
|
||||||
|
|
||||||
// URL returns the Gitea instance URL.
|
// URL returns the Gitea instance URL.
|
||||||
|
// Usage: URL(...)
|
||||||
func (c *Client) URL() string { return c.url }
|
func (c *Client) URL() string { return c.url }
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ func TestNew_Good(t *testing.T) {
|
||||||
assert.Equal(t, srv.URL, client.URL())
|
assert.Equal(t, srv.URL, client.URL())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNew_Bad_InvalidURL(t *testing.T) {
|
func TestNew_Bad_InvalidURL_Good(t *testing.T) {
|
||||||
_, err := New("://invalid-url", "token")
|
_, err := New("://invalid-url", "token")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ func isolateConfigEnv(t *testing.T) {
|
||||||
t.Setenv("HOME", t.TempDir())
|
t.Setenv("HOME", t.TempDir())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveConfig_Good_Defaults(t *testing.T) {
|
func TestResolveConfig_Good_Defaults_Good(t *testing.T) {
|
||||||
isolateConfigEnv(t)
|
isolateConfigEnv(t)
|
||||||
|
|
||||||
url, token, err := ResolveConfig("", "")
|
url, token, err := ResolveConfig("", "")
|
||||||
|
|
@ -26,7 +26,7 @@ func TestResolveConfig_Good_Defaults(t *testing.T) {
|
||||||
assert.Empty(t, token, "token should be empty when nothing configured")
|
assert.Empty(t, token, "token should be empty when nothing configured")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveConfig_Good_FlagsOverrideAll(t *testing.T) {
|
func TestResolveConfig_Good_FlagsOverrideAll_Good(t *testing.T) {
|
||||||
isolateConfigEnv(t)
|
isolateConfigEnv(t)
|
||||||
t.Setenv("GITEA_URL", "https://env-url.example.com")
|
t.Setenv("GITEA_URL", "https://env-url.example.com")
|
||||||
t.Setenv("GITEA_TOKEN", "env-token-abc")
|
t.Setenv("GITEA_TOKEN", "env-token-abc")
|
||||||
|
|
@ -37,7 +37,7 @@ func TestResolveConfig_Good_FlagsOverrideAll(t *testing.T) {
|
||||||
assert.Equal(t, "flag-token-xyz", token, "flag token should override env")
|
assert.Equal(t, "flag-token-xyz", token, "flag token should override env")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveConfig_Good_EnvVarsOverrideConfig(t *testing.T) {
|
func TestResolveConfig_Good_EnvVarsOverrideConfig_Good(t *testing.T) {
|
||||||
isolateConfigEnv(t)
|
isolateConfigEnv(t)
|
||||||
t.Setenv("GITEA_URL", "https://env-url.example.com")
|
t.Setenv("GITEA_URL", "https://env-url.example.com")
|
||||||
t.Setenv("GITEA_TOKEN", "env-token-123")
|
t.Setenv("GITEA_TOKEN", "env-token-123")
|
||||||
|
|
@ -48,7 +48,7 @@ func TestResolveConfig_Good_EnvVarsOverrideConfig(t *testing.T) {
|
||||||
assert.Equal(t, "env-token-123", token)
|
assert.Equal(t, "env-token-123", token)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveConfig_Good_PartialOverrides(t *testing.T) {
|
func TestResolveConfig_Good_PartialOverrides_Good(t *testing.T) {
|
||||||
isolateConfigEnv(t)
|
isolateConfigEnv(t)
|
||||||
t.Setenv("GITEA_URL", "https://env-only.example.com")
|
t.Setenv("GITEA_URL", "https://env-only.example.com")
|
||||||
|
|
||||||
|
|
@ -58,7 +58,7 @@ func TestResolveConfig_Good_PartialOverrides(t *testing.T) {
|
||||||
assert.Equal(t, "flag-only-token", token, "flag token should be used")
|
assert.Equal(t, "flag-only-token", token, "flag token should be used")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveConfig_Good_URLDefaultsWhenEmpty(t *testing.T) {
|
func TestResolveConfig_Good_URLDefaultsWhenEmpty_Good(t *testing.T) {
|
||||||
isolateConfigEnv(t)
|
isolateConfigEnv(t)
|
||||||
t.Setenv("GITEA_TOKEN", "some-token")
|
t.Setenv("GITEA_TOKEN", "some-token")
|
||||||
|
|
||||||
|
|
@ -74,7 +74,7 @@ func TestConstants_Good(t *testing.T) {
|
||||||
assert.Equal(t, "https://gitea.snider.dev", DefaultURL)
|
assert.Equal(t, "https://gitea.snider.dev", DefaultURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewFromConfig_Bad_NoToken(t *testing.T) {
|
func TestNewFromConfig_Bad_NoToken_Good(t *testing.T) {
|
||||||
isolateConfigEnv(t)
|
isolateConfigEnv(t)
|
||||||
|
|
||||||
_, err := NewFromConfig("", "")
|
_, err := NewFromConfig("", "")
|
||||||
|
|
@ -82,7 +82,7 @@ func TestNewFromConfig_Bad_NoToken(t *testing.T) {
|
||||||
assert.Contains(t, err.Error(), "no API token configured")
|
assert.Contains(t, err.Error(), "no API token configured")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewFromConfig_Good_WithFlagToken(t *testing.T) {
|
func TestNewFromConfig_Good_WithFlagToken_Good(t *testing.T) {
|
||||||
isolateConfigEnv(t)
|
isolateConfigEnv(t)
|
||||||
|
|
||||||
srv := newMockGiteaServer(t)
|
srv := newMockGiteaServer(t)
|
||||||
|
|
@ -94,7 +94,7 @@ func TestNewFromConfig_Good_WithFlagToken(t *testing.T) {
|
||||||
assert.Equal(t, srv.URL, client.URL())
|
assert.Equal(t, srv.URL, client.URL())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewFromConfig_Good_EnvToken(t *testing.T) {
|
func TestNewFromConfig_Good_EnvToken_Good(t *testing.T) {
|
||||||
isolateConfigEnv(t)
|
isolateConfigEnv(t)
|
||||||
|
|
||||||
srv := newMockGiteaServer(t)
|
srv := newMockGiteaServer(t)
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import (
|
||||||
|
|
||||||
// --- SaveConfig tests ---
|
// --- SaveConfig tests ---
|
||||||
|
|
||||||
func TestSaveConfig_Good_URLAndToken(t *testing.T) {
|
func TestSaveConfig_Good_URLAndToken_Good(t *testing.T) {
|
||||||
isolateConfigEnv(t)
|
isolateConfigEnv(t)
|
||||||
|
|
||||||
err := SaveConfig("https://gitea.example.com", "test-token-123")
|
err := SaveConfig("https://gitea.example.com", "test-token-123")
|
||||||
|
|
@ -25,7 +25,7 @@ func TestSaveConfig_Good_URLAndToken(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSaveConfig_Good_URLOnly(t *testing.T) {
|
func TestSaveConfig_Good_URLOnly_Good(t *testing.T) {
|
||||||
isolateConfigEnv(t)
|
isolateConfigEnv(t)
|
||||||
|
|
||||||
err := SaveConfig("https://gitea.example.com", "")
|
err := SaveConfig("https://gitea.example.com", "")
|
||||||
|
|
@ -34,7 +34,7 @@ func TestSaveConfig_Good_URLOnly(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSaveConfig_Good_TokenOnly(t *testing.T) {
|
func TestSaveConfig_Good_TokenOnly_Good(t *testing.T) {
|
||||||
isolateConfigEnv(t)
|
isolateConfigEnv(t)
|
||||||
|
|
||||||
err := SaveConfig("", "some-token")
|
err := SaveConfig("", "some-token")
|
||||||
|
|
@ -43,7 +43,7 @@ func TestSaveConfig_Good_TokenOnly(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSaveConfig_Good_Empty(t *testing.T) {
|
func TestSaveConfig_Good_Empty_Good(t *testing.T) {
|
||||||
isolateConfigEnv(t)
|
isolateConfigEnv(t)
|
||||||
|
|
||||||
err := SaveConfig("", "")
|
err := SaveConfig("", "")
|
||||||
|
|
@ -89,7 +89,7 @@ func newPaginatedOrgReposServer(t *testing.T) *httptest.Server {
|
||||||
return httptest.NewServer(mux)
|
return httptest.NewServer(mux)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_ListOrgRepos_Good_Pagination(t *testing.T) {
|
func TestClient_ListOrgRepos_Good_Pagination_Good(t *testing.T) {
|
||||||
srv := newPaginatedOrgReposServer(t)
|
srv := newPaginatedOrgReposServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -123,7 +123,7 @@ func newPaginatedUserReposServer(t *testing.T) *httptest.Server {
|
||||||
return httptest.NewServer(mux)
|
return httptest.NewServer(mux)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_ListUserRepos_Good_SinglePage(t *testing.T) {
|
func TestClient_ListUserRepos_Good_SinglePage_Good(t *testing.T) {
|
||||||
srv := newPaginatedUserReposServer(t)
|
srv := newPaginatedUserReposServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -175,7 +175,7 @@ func newPRMetaWithManyCommentsServer(t *testing.T) *httptest.Server {
|
||||||
return httptest.NewServer(mux)
|
return httptest.NewServer(mux)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_GetPRMeta_Good_CommentCount(t *testing.T) {
|
func TestClient_GetPRMeta_Good_CommentCount_Good(t *testing.T) {
|
||||||
srv := newPRMetaWithManyCommentsServer(t)
|
srv := newPRMetaWithManyCommentsServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -219,7 +219,7 @@ func newPRMetaWithNilDatesServer(t *testing.T) *httptest.Server {
|
||||||
return httptest.NewServer(mux)
|
return httptest.NewServer(mux)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_GetPRMeta_Good_MinimalFields(t *testing.T) {
|
func TestClient_GetPRMeta_Good_MinimalFields_Good(t *testing.T) {
|
||||||
srv := newPRMetaWithNilDatesServer(t)
|
srv := newPRMetaWithNilDatesServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -238,7 +238,7 @@ func TestClient_GetPRMeta_Good_MinimalFields(t *testing.T) {
|
||||||
|
|
||||||
// --- GetCommentBodies: empty result ---
|
// --- GetCommentBodies: empty result ---
|
||||||
|
|
||||||
func TestClient_GetCommentBodies_Good_Empty(t *testing.T) {
|
func TestClient_GetCommentBodies_Good_Empty_Good(t *testing.T) {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("/api/v1/version", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/api/v1/version", func(w http.ResponseWriter, r *http.Request) {
|
||||||
jsonResponse(w, map[string]string{"version": "1.21.0"})
|
jsonResponse(w, map[string]string{"version": "1.21.0"})
|
||||||
|
|
@ -263,7 +263,7 @@ func TestClient_GetCommentBodies_Good_Empty(t *testing.T) {
|
||||||
|
|
||||||
// --- GetCommentBodies: poster is nil ---
|
// --- GetCommentBodies: poster is nil ---
|
||||||
|
|
||||||
func TestClient_GetCommentBodies_Good_NilPoster(t *testing.T) {
|
func TestClient_GetCommentBodies_Good_NilPoster_Good(t *testing.T) {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("/api/v1/version", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/api/v1/version", func(w http.ResponseWriter, r *http.Request) {
|
||||||
jsonResponse(w, map[string]string{"version": "1.21.0"})
|
jsonResponse(w, map[string]string{"version": "1.21.0"})
|
||||||
|
|
@ -293,7 +293,7 @@ func TestClient_GetCommentBodies_Good_NilPoster(t *testing.T) {
|
||||||
|
|
||||||
// --- ListPullRequests: state mapping ---
|
// --- ListPullRequests: state mapping ---
|
||||||
|
|
||||||
func TestClient_ListPullRequests_Good_AllStates(t *testing.T) {
|
func TestClient_ListPullRequests_Good_AllStates_Good(t *testing.T) {
|
||||||
client, srv := newTestClient(t)
|
client, srv := newTestClient(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -305,7 +305,7 @@ func TestClient_ListPullRequests_Good_AllStates(t *testing.T) {
|
||||||
|
|
||||||
// --- NewFromConfig: additional paths ---
|
// --- NewFromConfig: additional paths ---
|
||||||
|
|
||||||
func TestNewFromConfig_Good_FlagOverridesEnv(t *testing.T) {
|
func TestNewFromConfig_Good_FlagOverridesEnv_Good(t *testing.T) {
|
||||||
isolateConfigEnv(t)
|
isolateConfigEnv(t)
|
||||||
|
|
||||||
srv := newMockGiteaServer(t)
|
srv := newMockGiteaServer(t)
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ func TestClient_ListIssues_Good(t *testing.T) {
|
||||||
assert.Equal(t, "Issue 1", issues[0].Title)
|
assert.Equal(t, "Issue 1", issues[0].Title)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_ListIssues_Good_StateMapping(t *testing.T) {
|
func TestClient_ListIssues_Good_StateMapping_Good(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
state string
|
state string
|
||||||
|
|
@ -43,7 +43,7 @@ func TestClient_ListIssues_Good_StateMapping(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_ListIssues_Good_CustomPageAndLimit(t *testing.T) {
|
func TestClient_ListIssues_Good_CustomPageAndLimit_Good(t *testing.T) {
|
||||||
client, srv := newTestClient(t)
|
client, srv := newTestClient(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -54,7 +54,7 @@ func TestClient_ListIssues_Good_CustomPageAndLimit(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_ListIssues_Bad_ServerError(t *testing.T) {
|
func TestClient_ListIssues_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -72,7 +72,7 @@ func TestClient_GetIssue_Good(t *testing.T) {
|
||||||
assert.Equal(t, "Issue 1", issue.Title)
|
assert.Equal(t, "Issue 1", issue.Title)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_GetIssue_Bad_ServerError(t *testing.T) {
|
func TestClient_GetIssue_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -93,7 +93,7 @@ func TestClient_CreateIssue_Good(t *testing.T) {
|
||||||
assert.NotNil(t, issue)
|
assert.NotNil(t, issue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_CreateIssue_Bad_ServerError(t *testing.T) {
|
func TestClient_CreateIssue_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -114,7 +114,7 @@ func TestClient_ListPullRequests_Good(t *testing.T) {
|
||||||
assert.Equal(t, "PR 1", prs[0].Title)
|
assert.Equal(t, "PR 1", prs[0].Title)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_ListPullRequests_Good_StateMapping(t *testing.T) {
|
func TestClient_ListPullRequests_Good_StateMapping_Good(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
state string
|
state string
|
||||||
|
|
@ -136,7 +136,7 @@ func TestClient_ListPullRequests_Good_StateMapping(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_ListPullRequests_Bad_ServerError(t *testing.T) {
|
func TestClient_ListPullRequests_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -154,7 +154,7 @@ func TestClient_GetPullRequest_Good(t *testing.T) {
|
||||||
assert.Equal(t, "PR 1", pr.Title)
|
assert.Equal(t, "PR 1", pr.Title)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_GetPullRequest_Bad_ServerError(t *testing.T) {
|
func TestClient_GetPullRequest_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -165,7 +165,7 @@ func TestClient_GetPullRequest_Bad_ServerError(t *testing.T) {
|
||||||
|
|
||||||
// --- ListIssuesOpts defaulting ---
|
// --- ListIssuesOpts defaulting ---
|
||||||
|
|
||||||
func TestListIssuesOpts_Good_Defaults(t *testing.T) {
|
func TestListIssuesOpts_Good_Defaults_Good(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
opts ListIssuesOpts
|
opts ListIssuesOpts
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ const commentPageSize = 50
|
||||||
|
|
||||||
// GetPRMeta returns structural signals for a pull request.
|
// GetPRMeta returns structural signals for a pull request.
|
||||||
// This is the Gitea side of the dual MetaReader described in the pipeline design.
|
// This is the Gitea side of the dual MetaReader described in the pipeline design.
|
||||||
|
// Usage: GetPRMeta(...)
|
||||||
func (c *Client) GetPRMeta(owner, repo string, pr int64) (*PRMeta, error) {
|
func (c *Client) GetPRMeta(owner, repo string, pr int64) (*PRMeta, error) {
|
||||||
pull, _, err := c.api.GetPullRequest(owner, repo, pr)
|
pull, _, err := c.api.GetPullRequest(owner, repo, pr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -98,6 +99,7 @@ func (c *Client) GetPRMeta(owner, repo string, pr int64) (*PRMeta, error) {
|
||||||
|
|
||||||
// GetCommentBodies returns all comment bodies for a pull request.
|
// GetCommentBodies returns all comment bodies for a pull request.
|
||||||
// This reads full content, which is safe on the home lab Gitea instance.
|
// This reads full content, which is safe on the home lab Gitea instance.
|
||||||
|
// Usage: GetCommentBodies(...)
|
||||||
func (c *Client) GetCommentBodies(owner, repo string, pr int64) ([]Comment, error) {
|
func (c *Client) GetCommentBodies(owner, repo string, pr int64) ([]Comment, error) {
|
||||||
var comments []Comment
|
var comments []Comment
|
||||||
page := 1
|
page := 1
|
||||||
|
|
@ -138,6 +140,7 @@ func (c *Client) GetCommentBodies(owner, repo string, pr int64) ([]Comment, erro
|
||||||
|
|
||||||
// GetIssueBody returns the body text of an issue.
|
// GetIssueBody returns the body text of an issue.
|
||||||
// This reads full content, which is safe on the home lab Gitea instance.
|
// This reads full content, which is safe on the home lab Gitea instance.
|
||||||
|
// Usage: GetIssueBody(...)
|
||||||
func (c *Client) GetIssueBody(owner, repo string, issue int64) (string, error) {
|
func (c *Client) GetIssueBody(owner, repo string, issue int64) (string, error) {
|
||||||
iss, _, err := c.api.GetIssue(owner, repo, issue)
|
iss, _, err := c.api.GetIssue(owner, repo, issue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ func TestClient_GetPRMeta_Good(t *testing.T) {
|
||||||
assert.False(t, meta.IsMerged)
|
assert.False(t, meta.IsMerged)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_GetPRMeta_Bad_ServerError(t *testing.T) {
|
func TestClient_GetPRMeta_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -47,7 +47,7 @@ func TestClient_GetCommentBodies_Good(t *testing.T) {
|
||||||
assert.Equal(t, "user2", comments[1].Author)
|
assert.Equal(t, "user2", comments[1].Author)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_GetCommentBodies_Bad_ServerError(t *testing.T) {
|
func TestClient_GetCommentBodies_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -65,7 +65,7 @@ func TestClient_GetIssueBody_Good(t *testing.T) {
|
||||||
assert.Equal(t, "First issue body", body)
|
assert.Equal(t, "First issue body", body)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_GetIssueBody_Bad_ServerError(t *testing.T) {
|
func TestClient_GetIssueBody_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -76,7 +76,7 @@ func TestClient_GetIssueBody_Bad_ServerError(t *testing.T) {
|
||||||
|
|
||||||
// --- PRMeta struct tests ---
|
// --- PRMeta struct tests ---
|
||||||
|
|
||||||
func TestPRMeta_Good_Fields(t *testing.T) {
|
func TestPRMeta_Good_Fields_Good(t *testing.T) {
|
||||||
meta := &PRMeta{
|
meta := &PRMeta{
|
||||||
Number: 42,
|
Number: 42,
|
||||||
Title: "Test PR",
|
Title: "Test PR",
|
||||||
|
|
@ -102,7 +102,7 @@ func TestPRMeta_Good_Fields(t *testing.T) {
|
||||||
assert.Equal(t, 5, meta.CommentCount)
|
assert.Equal(t, 5, meta.CommentCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestComment_Good_Fields(t *testing.T) {
|
func TestComment_Good_Fields_Good(t *testing.T) {
|
||||||
comment := Comment{
|
comment := Comment{
|
||||||
ID: 123,
|
ID: 123,
|
||||||
Author: "reviewer",
|
Author: "reviewer",
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ func TestClient_ListOrgRepos_Good(t *testing.T) {
|
||||||
assert.Equal(t, "org-repo", repos[0].Name)
|
assert.Equal(t, "org-repo", repos[0].Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_ListOrgRepos_Bad_ServerError(t *testing.T) {
|
func TestClient_ListOrgRepos_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -41,7 +41,7 @@ func TestClient_ListUserRepos_Good(t *testing.T) {
|
||||||
assert.Equal(t, "repo-b", repos[1].Name)
|
assert.Equal(t, "repo-b", repos[1].Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_ListUserRepos_Bad_ServerError(t *testing.T) {
|
func TestClient_ListUserRepos_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -59,7 +59,7 @@ func TestClient_GetRepo_Good(t *testing.T) {
|
||||||
assert.Equal(t, "org-repo", repo.Name)
|
assert.Equal(t, "org-repo", repo.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_GetRepo_Bad_ServerError(t *testing.T) {
|
func TestClient_GetRepo_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -68,7 +68,7 @@ func TestClient_GetRepo_Bad_ServerError(t *testing.T) {
|
||||||
assert.Contains(t, err.Error(), "failed to get repo")
|
assert.Contains(t, err.Error(), "failed to get repo")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_CreateMirror_Good_WithAuth(t *testing.T) {
|
func TestClient_CreateMirror_Good_WithAuth_Good(t *testing.T) {
|
||||||
client, srv := newTestClient(t)
|
client, srv := newTestClient(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -78,7 +78,7 @@ func TestClient_CreateMirror_Good_WithAuth(t *testing.T) {
|
||||||
assert.NotNil(t, repo)
|
assert.NotNil(t, repo)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_CreateMirror_Bad_NoAuthToken(t *testing.T) {
|
func TestClient_CreateMirror_Bad_NoAuthToken_Good(t *testing.T) {
|
||||||
client, srv := newTestClient(t)
|
client, srv := newTestClient(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -88,7 +88,7 @@ func TestClient_CreateMirror_Bad_NoAuthToken(t *testing.T) {
|
||||||
assert.Contains(t, err.Error(), "failed to create mirror")
|
assert.Contains(t, err.Error(), "failed to create mirror")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_CreateMirror_Bad_ServerError(t *testing.T) {
|
func TestClient_CreateMirror_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -105,7 +105,7 @@ func TestClient_DeleteRepo_Good(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_DeleteRepo_Bad_ServerError(t *testing.T) {
|
func TestClient_DeleteRepo_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -126,7 +126,7 @@ func TestClient_CreateOrgRepo_Good(t *testing.T) {
|
||||||
assert.NotNil(t, repo)
|
assert.NotNil(t, repo)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_CreateOrgRepo_Bad_ServerError(t *testing.T) {
|
func TestClient_CreateOrgRepo_Bad_ServerError_Good(t *testing.T) {
|
||||||
client, srv := newErrorServer(t)
|
client, srv := newErrorServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ func newTestClient(t *testing.T, url string) *forge.Client {
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestForgejoSource_Good_Name(t *testing.T) {
|
func TestForgejoSource_Good_Name_Good(t *testing.T) {
|
||||||
s := New(Config{}, nil)
|
s := New(Config{}, nil)
|
||||||
assert.Equal(t, "forgejo", s.Name())
|
assert.Equal(t, "forgejo", s.Name())
|
||||||
}
|
}
|
||||||
|
|
@ -108,7 +108,7 @@ func TestForgejoSource_Poll_Good(t *testing.T) {
|
||||||
assert.Equal(t, "abc123", sig.LastCommitSHA)
|
assert.Equal(t, "abc123", sig.LastCommitSHA)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestForgejoSource_Poll_Good_NoEpics(t *testing.T) {
|
func TestForgejoSource_Poll_Good_NoEpics_Good(t *testing.T) {
|
||||||
srv := httptest.NewServer(withVersion(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(withVersion(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
_ = json.NewEncoder(w).Encode([]any{})
|
_ = json.NewEncoder(w).Encode([]any{})
|
||||||
|
|
|
||||||
|
|
@ -30,16 +30,19 @@ func NewCompletionHandler(client *forge.Client) *CompletionHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name returns the handler identifier.
|
// Name returns the handler identifier.
|
||||||
|
// Usage: Name(...)
|
||||||
func (h *CompletionHandler) Name() string {
|
func (h *CompletionHandler) Name() string {
|
||||||
return "completion"
|
return "completion"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match returns true if the signal indicates an agent has finished a task.
|
// Match returns true if the signal indicates an agent has finished a task.
|
||||||
|
// Usage: Match(...)
|
||||||
func (h *CompletionHandler) Match(signal *jobrunner.PipelineSignal) bool {
|
func (h *CompletionHandler) Match(signal *jobrunner.PipelineSignal) bool {
|
||||||
return signal.Type == "agent_completion"
|
return signal.Type == "agent_completion"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute updates the issue labels based on the completion status.
|
// Execute updates the issue labels based on the completion status.
|
||||||
|
// Usage: Execute(...)
|
||||||
func (h *CompletionHandler) Execute(ctx context.Context, signal *jobrunner.PipelineSignal) (*jobrunner.ActionResult, error) {
|
func (h *CompletionHandler) Execute(ctx context.Context, signal *jobrunner.PipelineSignal) (*jobrunner.ActionResult, error) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ func newTestSpinner(agents map[string]agentci.AgentConfig) *agentci.Spinner {
|
||||||
|
|
||||||
// --- Match tests ---
|
// --- Match tests ---
|
||||||
|
|
||||||
func TestDispatch_Match_Good_NeedsCoding(t *testing.T) {
|
func TestDispatch_Match_Good_NeedsCoding_Good(t *testing.T) {
|
||||||
spinner := newTestSpinner(map[string]agentci.AgentConfig{
|
spinner := newTestSpinner(map[string]agentci.AgentConfig{
|
||||||
"darbs-claude": {Host: "claude@192.168.0.201", QueueDir: "~/ai-work/queue", Active: true},
|
"darbs-claude": {Host: "claude@192.168.0.201", QueueDir: "~/ai-work/queue", Active: true},
|
||||||
})
|
})
|
||||||
|
|
@ -49,7 +49,7 @@ func TestDispatch_Match_Good_NeedsCoding(t *testing.T) {
|
||||||
assert.True(t, h.Match(sig))
|
assert.True(t, h.Match(sig))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDispatch_Match_Good_MultipleAgents(t *testing.T) {
|
func TestDispatch_Match_Good_MultipleAgents_Good(t *testing.T) {
|
||||||
spinner := newTestSpinner(map[string]agentci.AgentConfig{
|
spinner := newTestSpinner(map[string]agentci.AgentConfig{
|
||||||
"darbs-claude": {Host: "claude@192.168.0.201", QueueDir: "~/ai-work/queue", Active: true},
|
"darbs-claude": {Host: "claude@192.168.0.201", QueueDir: "~/ai-work/queue", Active: true},
|
||||||
"local-codex": {Host: "localhost", QueueDir: "~/ai-work/queue", Active: true},
|
"local-codex": {Host: "localhost", QueueDir: "~/ai-work/queue", Active: true},
|
||||||
|
|
@ -62,7 +62,7 @@ func TestDispatch_Match_Good_MultipleAgents(t *testing.T) {
|
||||||
assert.True(t, h.Match(sig))
|
assert.True(t, h.Match(sig))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDispatch_Match_Bad_HasPR(t *testing.T) {
|
func TestDispatch_Match_Bad_HasPR_Good(t *testing.T) {
|
||||||
spinner := newTestSpinner(map[string]agentci.AgentConfig{
|
spinner := newTestSpinner(map[string]agentci.AgentConfig{
|
||||||
"darbs-claude": {Host: "claude@192.168.0.201", QueueDir: "~/ai-work/queue", Active: true},
|
"darbs-claude": {Host: "claude@192.168.0.201", QueueDir: "~/ai-work/queue", Active: true},
|
||||||
})
|
})
|
||||||
|
|
@ -75,7 +75,7 @@ func TestDispatch_Match_Bad_HasPR(t *testing.T) {
|
||||||
assert.False(t, h.Match(sig))
|
assert.False(t, h.Match(sig))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDispatch_Match_Bad_UnknownAgent(t *testing.T) {
|
func TestDispatch_Match_Bad_UnknownAgent_Good(t *testing.T) {
|
||||||
spinner := newTestSpinner(map[string]agentci.AgentConfig{
|
spinner := newTestSpinner(map[string]agentci.AgentConfig{
|
||||||
"darbs-claude": {Host: "claude@192.168.0.201", QueueDir: "~/ai-work/queue", Active: true},
|
"darbs-claude": {Host: "claude@192.168.0.201", QueueDir: "~/ai-work/queue", Active: true},
|
||||||
})
|
})
|
||||||
|
|
@ -87,7 +87,7 @@ func TestDispatch_Match_Bad_UnknownAgent(t *testing.T) {
|
||||||
assert.False(t, h.Match(sig))
|
assert.False(t, h.Match(sig))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDispatch_Match_Bad_NotAssigned(t *testing.T) {
|
func TestDispatch_Match_Bad_NotAssigned_Good(t *testing.T) {
|
||||||
spinner := newTestSpinner(map[string]agentci.AgentConfig{
|
spinner := newTestSpinner(map[string]agentci.AgentConfig{
|
||||||
"darbs-claude": {Host: "claude@192.168.0.201", QueueDir: "~/ai-work/queue", Active: true},
|
"darbs-claude": {Host: "claude@192.168.0.201", QueueDir: "~/ai-work/queue", Active: true},
|
||||||
})
|
})
|
||||||
|
|
@ -99,7 +99,7 @@ func TestDispatch_Match_Bad_NotAssigned(t *testing.T) {
|
||||||
assert.False(t, h.Match(sig))
|
assert.False(t, h.Match(sig))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDispatch_Match_Bad_EmptyAgentMap(t *testing.T) {
|
func TestDispatch_Match_Bad_EmptyAgentMap_Good(t *testing.T) {
|
||||||
spinner := newTestSpinner(map[string]agentci.AgentConfig{})
|
spinner := newTestSpinner(map[string]agentci.AgentConfig{})
|
||||||
h := NewDispatchHandler(nil, "", "", spinner)
|
h := NewDispatchHandler(nil, "", "", spinner)
|
||||||
sig := &jobrunner.PipelineSignal{
|
sig := &jobrunner.PipelineSignal{
|
||||||
|
|
@ -119,7 +119,7 @@ func TestDispatch_Name_Good(t *testing.T) {
|
||||||
|
|
||||||
// --- Execute tests ---
|
// --- Execute tests ---
|
||||||
|
|
||||||
func TestDispatch_Execute_Bad_UnknownAgent(t *testing.T) {
|
func TestDispatch_Execute_Bad_UnknownAgent_Good(t *testing.T) {
|
||||||
srv := httptest.NewServer(withVersion(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(withVersion(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
})))
|
})))
|
||||||
|
|
@ -144,7 +144,7 @@ func TestDispatch_Execute_Bad_UnknownAgent(t *testing.T) {
|
||||||
assert.Contains(t, err.Error(), "unknown agent")
|
assert.Contains(t, err.Error(), "unknown agent")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDispatch_Execute_Bad_InvalidQueueDir(t *testing.T) {
|
func TestDispatch_Execute_Bad_InvalidQueueDir_Good(t *testing.T) {
|
||||||
spinner := newTestSpinner(map[string]agentci.AgentConfig{
|
spinner := newTestSpinner(map[string]agentci.AgentConfig{
|
||||||
"darbs-claude": {
|
"darbs-claude": {
|
||||||
Host: "localhost",
|
Host: "localhost",
|
||||||
|
|
@ -209,7 +209,7 @@ func TestDispatch_TicketJSON_Good(t *testing.T) {
|
||||||
assert.False(t, hasToken, "forge_token must not be in ticket JSON")
|
assert.False(t, hasToken, "forge_token must not be in ticket JSON")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDispatch_TicketJSON_Good_DualRun(t *testing.T) {
|
func TestDispatch_TicketJSON_Good_DualRun_Good(t *testing.T) {
|
||||||
ticket := DispatchTicket{
|
ticket := DispatchTicket{
|
||||||
ID: "test-dual",
|
ID: "test-dual",
|
||||||
RepoOwner: "host-uk",
|
RepoOwner: "host-uk",
|
||||||
|
|
@ -231,7 +231,7 @@ func TestDispatch_TicketJSON_Good_DualRun(t *testing.T) {
|
||||||
assert.Equal(t, "gemini-1.5-pro", roundtrip.VerifyModel)
|
assert.Equal(t, "gemini-1.5-pro", roundtrip.VerifyModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDispatch_TicketJSON_Good_OmitsEmptyModelRunner(t *testing.T) {
|
func TestDispatch_TicketJSON_Good_OmitsEmptyModelRunner_Good(t *testing.T) {
|
||||||
ticket := DispatchTicket{
|
ticket := DispatchTicket{
|
||||||
ID: "test-1",
|
ID: "test-1",
|
||||||
RepoOwner: "host-uk",
|
RepoOwner: "host-uk",
|
||||||
|
|
@ -254,7 +254,7 @@ func TestDispatch_TicketJSON_Good_OmitsEmptyModelRunner(t *testing.T) {
|
||||||
assert.False(t, hasRunner, "runner should be omitted when empty")
|
assert.False(t, hasRunner, "runner should be omitted when empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDispatch_runRemote_Good_EscapesPath(t *testing.T) {
|
func TestDispatch_runRemote_Good_EscapesPath_Good(t *testing.T) {
|
||||||
outputPath := filepath.Join(t.TempDir(), "ssh-output.txt")
|
outputPath := filepath.Join(t.TempDir(), "ssh-output.txt")
|
||||||
toolPath := writeFakeSSHCommand(t, outputPath)
|
toolPath := writeFakeSSHCommand(t, outputPath)
|
||||||
t.Setenv("PATH", toolPath+":"+os.Getenv("PATH"))
|
t.Setenv("PATH", toolPath+":"+os.Getenv("PATH"))
|
||||||
|
|
@ -275,7 +275,7 @@ func TestDispatch_runRemote_Good_EscapesPath(t *testing.T) {
|
||||||
assert.Contains(t, string(output), "rm '-f' '"+dangerousPath+"'\n")
|
assert.Contains(t, string(output), "rm '-f' '"+dangerousPath+"'\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDispatch_secureTransfer_Good_EscapesPath(t *testing.T) {
|
func TestDispatch_secureTransfer_Good_EscapesPath_Good(t *testing.T) {
|
||||||
outputPath := filepath.Join(t.TempDir(), "ssh-output.txt")
|
outputPath := filepath.Join(t.TempDir(), "ssh-output.txt")
|
||||||
toolPath := writeFakeSSHCommand(t, outputPath)
|
toolPath := writeFakeSSHCommand(t, outputPath)
|
||||||
t.Setenv("PATH", toolPath+":"+os.Getenv("PATH"))
|
t.Setenv("PATH", toolPath+":"+os.Getenv("PATH"))
|
||||||
|
|
@ -301,7 +301,7 @@ func TestDispatch_secureTransfer_Good_EscapesPath(t *testing.T) {
|
||||||
assert.Equal(t, "hello", string(input))
|
assert.Equal(t, "hello", string(input))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDispatch_TicketJSON_Good_ModelRunnerVariants(t *testing.T) {
|
func TestDispatch_TicketJSON_Good_ModelRunnerVariants_Good(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
model string
|
model string
|
||||||
|
|
@ -338,7 +338,7 @@ func TestDispatch_TicketJSON_Good_ModelRunnerVariants(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDispatch_Execute_Good_PostsComment(t *testing.T) {
|
func TestDispatch_Execute_Good_PostsComment_Good(t *testing.T) {
|
||||||
var commentPosted bool
|
var commentPosted bool
|
||||||
var commentBody string
|
var commentBody string
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,12 +22,14 @@ func NewEnableAutoMergeHandler(f *forge.Client) *EnableAutoMergeHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name returns the handler identifier.
|
// Name returns the handler identifier.
|
||||||
|
// Usage: Name(...)
|
||||||
func (h *EnableAutoMergeHandler) Name() string {
|
func (h *EnableAutoMergeHandler) Name() string {
|
||||||
return "enable_auto_merge"
|
return "enable_auto_merge"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match returns true when the PR is open, not a draft, mergeable, checks
|
// Match returns true when the PR is open, not a draft, mergeable, checks
|
||||||
// are passing, and there are no unresolved review threads.
|
// are passing, and there are no unresolved review threads.
|
||||||
|
// Usage: Match(...)
|
||||||
func (h *EnableAutoMergeHandler) Match(signal *jobrunner.PipelineSignal) bool {
|
func (h *EnableAutoMergeHandler) Match(signal *jobrunner.PipelineSignal) bool {
|
||||||
return signal.PRState == "OPEN" &&
|
return signal.PRState == "OPEN" &&
|
||||||
!signal.IsDraft &&
|
!signal.IsDraft &&
|
||||||
|
|
@ -37,6 +39,7 @@ func (h *EnableAutoMergeHandler) Match(signal *jobrunner.PipelineSignal) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute merges the pull request with squash strategy.
|
// Execute merges the pull request with squash strategy.
|
||||||
|
// Usage: Execute(...)
|
||||||
func (h *EnableAutoMergeHandler) Execute(ctx context.Context, signal *jobrunner.PipelineSignal) (*jobrunner.ActionResult, error) {
|
func (h *EnableAutoMergeHandler) Execute(ctx context.Context, signal *jobrunner.PipelineSignal) (*jobrunner.ActionResult, error) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ func TestEnableAutoMerge_Match_Good(t *testing.T) {
|
||||||
assert.True(t, h.Match(sig))
|
assert.True(t, h.Match(sig))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEnableAutoMerge_Match_Bad_Draft(t *testing.T) {
|
func TestEnableAutoMerge_Match_Bad_Draft_Good(t *testing.T) {
|
||||||
h := NewEnableAutoMergeHandler(nil)
|
h := NewEnableAutoMergeHandler(nil)
|
||||||
sig := &jobrunner.PipelineSignal{
|
sig := &jobrunner.PipelineSignal{
|
||||||
PRState: "OPEN",
|
PRState: "OPEN",
|
||||||
|
|
@ -41,7 +41,7 @@ func TestEnableAutoMerge_Match_Bad_Draft(t *testing.T) {
|
||||||
assert.False(t, h.Match(sig))
|
assert.False(t, h.Match(sig))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEnableAutoMerge_Match_Bad_UnresolvedThreads(t *testing.T) {
|
func TestEnableAutoMerge_Match_Bad_UnresolvedThreads_Good(t *testing.T) {
|
||||||
h := NewEnableAutoMergeHandler(nil)
|
h := NewEnableAutoMergeHandler(nil)
|
||||||
sig := &jobrunner.PipelineSignal{
|
sig := &jobrunner.PipelineSignal{
|
||||||
PRState: "OPEN",
|
PRState: "OPEN",
|
||||||
|
|
@ -83,7 +83,7 @@ func TestEnableAutoMerge_Execute_Good(t *testing.T) {
|
||||||
assert.Equal(t, "/api/v1/repos/host-uk/core-php/pulls/55/merge", capturedPath)
|
assert.Equal(t, "/api/v1/repos/host-uk/core-php/pulls/55/merge", capturedPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEnableAutoMerge_Execute_Bad_MergeFailed(t *testing.T) {
|
func TestEnableAutoMerge_Execute_Bad_MergeFailed_Good(t *testing.T) {
|
||||||
srv := httptest.NewServer(withVersion(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(withVersion(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusConflict)
|
w.WriteHeader(http.StatusConflict)
|
||||||
_ = json.NewEncoder(w).Encode(map[string]string{"message": "merge conflict"})
|
_ = json.NewEncoder(w).Encode(map[string]string{"message": "merge conflict"})
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,13 @@ func NewPublishDraftHandler(f *forge.Client) *PublishDraftHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name returns the handler identifier.
|
// Name returns the handler identifier.
|
||||||
|
// Usage: Name(...)
|
||||||
func (h *PublishDraftHandler) Name() string {
|
func (h *PublishDraftHandler) Name() string {
|
||||||
return "publish_draft"
|
return "publish_draft"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match returns true when the PR is a draft, open, and all checks have passed.
|
// Match returns true when the PR is a draft, open, and all checks have passed.
|
||||||
|
// Usage: Match(...)
|
||||||
func (h *PublishDraftHandler) Match(signal *jobrunner.PipelineSignal) bool {
|
func (h *PublishDraftHandler) Match(signal *jobrunner.PipelineSignal) bool {
|
||||||
return signal.IsDraft &&
|
return signal.IsDraft &&
|
||||||
signal.PRState == "OPEN" &&
|
signal.PRState == "OPEN" &&
|
||||||
|
|
@ -34,6 +36,7 @@ func (h *PublishDraftHandler) Match(signal *jobrunner.PipelineSignal) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute marks the PR as no longer a draft.
|
// Execute marks the PR as no longer a draft.
|
||||||
|
// Usage: Execute(...)
|
||||||
func (h *PublishDraftHandler) Execute(ctx context.Context, signal *jobrunner.PipelineSignal) (*jobrunner.ActionResult, error) {
|
func (h *PublishDraftHandler) Execute(ctx context.Context, signal *jobrunner.PipelineSignal) (*jobrunner.ActionResult, error) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ func TestPublishDraft_Match_Good(t *testing.T) {
|
||||||
assert.True(t, h.Match(sig))
|
assert.True(t, h.Match(sig))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPublishDraft_Match_Bad_NotDraft(t *testing.T) {
|
func TestPublishDraft_Match_Bad_NotDraft_Good(t *testing.T) {
|
||||||
h := NewPublishDraftHandler(nil)
|
h := NewPublishDraftHandler(nil)
|
||||||
sig := &jobrunner.PipelineSignal{
|
sig := &jobrunner.PipelineSignal{
|
||||||
IsDraft: false,
|
IsDraft: false,
|
||||||
|
|
@ -35,7 +35,7 @@ func TestPublishDraft_Match_Bad_NotDraft(t *testing.T) {
|
||||||
assert.False(t, h.Match(sig))
|
assert.False(t, h.Match(sig))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPublishDraft_Match_Bad_ChecksFailing(t *testing.T) {
|
func TestPublishDraft_Match_Bad_ChecksFailing_Good(t *testing.T) {
|
||||||
h := NewPublishDraftHandler(nil)
|
h := NewPublishDraftHandler(nil)
|
||||||
sig := &jobrunner.PipelineSignal{
|
sig := &jobrunner.PipelineSignal{
|
||||||
IsDraft: true,
|
IsDraft: true,
|
||||||
|
|
|
||||||
|
|
@ -27,16 +27,19 @@ func NewDismissReviewsHandler(f *forge.Client) *DismissReviewsHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name returns the handler identifier.
|
// Name returns the handler identifier.
|
||||||
|
// Usage: Name(...)
|
||||||
func (h *DismissReviewsHandler) Name() string {
|
func (h *DismissReviewsHandler) Name() string {
|
||||||
return "dismiss_reviews"
|
return "dismiss_reviews"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match returns true when the PR is open and has unresolved review threads.
|
// Match returns true when the PR is open and has unresolved review threads.
|
||||||
|
// Usage: Match(...)
|
||||||
func (h *DismissReviewsHandler) Match(signal *jobrunner.PipelineSignal) bool {
|
func (h *DismissReviewsHandler) Match(signal *jobrunner.PipelineSignal) bool {
|
||||||
return signal.PRState == "OPEN" && signal.HasUnresolvedThreads()
|
return signal.PRState == "OPEN" && signal.HasUnresolvedThreads()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute dismisses stale "request changes" reviews on the PR.
|
// Execute dismisses stale "request changes" reviews on the PR.
|
||||||
|
// Usage: Execute(...)
|
||||||
func (h *DismissReviewsHandler) Execute(ctx context.Context, signal *jobrunner.PipelineSignal) (*jobrunner.ActionResult, error) {
|
func (h *DismissReviewsHandler) Execute(ctx context.Context, signal *jobrunner.PipelineSignal) (*jobrunner.ActionResult, error) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ func TestDismissReviews_Match_Good(t *testing.T) {
|
||||||
assert.True(t, h.Match(sig))
|
assert.True(t, h.Match(sig))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDismissReviews_Match_Bad_AllResolved(t *testing.T) {
|
func TestDismissReviews_Match_Bad_AllResolved_Good(t *testing.T) {
|
||||||
h := NewDismissReviewsHandler(nil)
|
h := NewDismissReviewsHandler(nil)
|
||||||
sig := &jobrunner.PipelineSignal{
|
sig := &jobrunner.PipelineSignal{
|
||||||
PRState: "OPEN",
|
PRState: "OPEN",
|
||||||
|
|
|
||||||
|
|
@ -23,12 +23,14 @@ func NewSendFixCommandHandler(f *forge.Client) *SendFixCommandHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name returns the handler identifier.
|
// Name returns the handler identifier.
|
||||||
|
// Usage: Name(...)
|
||||||
func (h *SendFixCommandHandler) Name() string {
|
func (h *SendFixCommandHandler) Name() string {
|
||||||
return "send_fix_command"
|
return "send_fix_command"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match returns true when the PR is open and either has merge conflicts or
|
// Match returns true when the PR is open and either has merge conflicts or
|
||||||
// has unresolved threads with failing checks.
|
// has unresolved threads with failing checks.
|
||||||
|
// Usage: Match(...)
|
||||||
func (h *SendFixCommandHandler) Match(signal *jobrunner.PipelineSignal) bool {
|
func (h *SendFixCommandHandler) Match(signal *jobrunner.PipelineSignal) bool {
|
||||||
if signal.PRState != "OPEN" {
|
if signal.PRState != "OPEN" {
|
||||||
return false
|
return false
|
||||||
|
|
@ -43,6 +45,7 @@ func (h *SendFixCommandHandler) Match(signal *jobrunner.PipelineSignal) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute posts a comment on the PR asking for a fix.
|
// Execute posts a comment on the PR asking for a fix.
|
||||||
|
// Usage: Execute(...)
|
||||||
func (h *SendFixCommandHandler) Execute(ctx context.Context, signal *jobrunner.PipelineSignal) (*jobrunner.ActionResult, error) {
|
func (h *SendFixCommandHandler) Execute(ctx context.Context, signal *jobrunner.PipelineSignal) (*jobrunner.ActionResult, error) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import (
|
||||||
"dappco.re/go/core/scm/jobrunner"
|
"dappco.re/go/core/scm/jobrunner"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSendFixCommand_Match_Good_Conflicting(t *testing.T) {
|
func TestSendFixCommand_Match_Good_Conflicting_Good(t *testing.T) {
|
||||||
h := NewSendFixCommandHandler(nil)
|
h := NewSendFixCommandHandler(nil)
|
||||||
sig := &jobrunner.PipelineSignal{
|
sig := &jobrunner.PipelineSignal{
|
||||||
PRState: "OPEN",
|
PRState: "OPEN",
|
||||||
|
|
@ -24,7 +24,7 @@ func TestSendFixCommand_Match_Good_Conflicting(t *testing.T) {
|
||||||
assert.True(t, h.Match(sig))
|
assert.True(t, h.Match(sig))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSendFixCommand_Match_Good_UnresolvedThreads(t *testing.T) {
|
func TestSendFixCommand_Match_Good_UnresolvedThreads_Good(t *testing.T) {
|
||||||
h := NewSendFixCommandHandler(nil)
|
h := NewSendFixCommandHandler(nil)
|
||||||
sig := &jobrunner.PipelineSignal{
|
sig := &jobrunner.PipelineSignal{
|
||||||
PRState: "OPEN",
|
PRState: "OPEN",
|
||||||
|
|
@ -36,7 +36,7 @@ func TestSendFixCommand_Match_Good_UnresolvedThreads(t *testing.T) {
|
||||||
assert.True(t, h.Match(sig))
|
assert.True(t, h.Match(sig))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSendFixCommand_Match_Bad_Clean(t *testing.T) {
|
func TestSendFixCommand_Match_Bad_Clean_Good(t *testing.T) {
|
||||||
h := NewSendFixCommandHandler(nil)
|
h := NewSendFixCommandHandler(nil)
|
||||||
sig := &jobrunner.PipelineSignal{
|
sig := &jobrunner.PipelineSignal{
|
||||||
PRState: "OPEN",
|
PRState: "OPEN",
|
||||||
|
|
@ -48,7 +48,7 @@ func TestSendFixCommand_Match_Bad_Clean(t *testing.T) {
|
||||||
assert.False(t, h.Match(sig))
|
assert.False(t, h.Match(sig))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSendFixCommand_Execute_Good_Conflict(t *testing.T) {
|
func TestSendFixCommand_Execute_Good_Conflict_Good(t *testing.T) {
|
||||||
var capturedMethod string
|
var capturedMethod string
|
||||||
var capturedPath string
|
var capturedPath string
|
||||||
var capturedBody string
|
var capturedBody string
|
||||||
|
|
|
||||||
|
|
@ -27,17 +27,20 @@ func NewTickParentHandler(f *forge.Client) *TickParentHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name returns the handler identifier.
|
// Name returns the handler identifier.
|
||||||
|
// Usage: Name(...)
|
||||||
func (h *TickParentHandler) Name() string {
|
func (h *TickParentHandler) Name() string {
|
||||||
return "tick_parent"
|
return "tick_parent"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match returns true when the child PR has been merged.
|
// Match returns true when the child PR has been merged.
|
||||||
|
// Usage: Match(...)
|
||||||
func (h *TickParentHandler) Match(signal *jobrunner.PipelineSignal) bool {
|
func (h *TickParentHandler) Match(signal *jobrunner.PipelineSignal) bool {
|
||||||
return signal.PRState == "MERGED"
|
return signal.PRState == "MERGED"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute fetches the epic body, replaces the unchecked checkbox for the
|
// Execute fetches the epic body, replaces the unchecked checkbox for the
|
||||||
// child issue with a checked one, updates the epic, and closes the child issue.
|
// child issue with a checked one, updates the epic, and closes the child issue.
|
||||||
|
// Usage: Execute(...)
|
||||||
func (h *TickParentHandler) Execute(ctx context.Context, signal *jobrunner.PipelineSignal) (*jobrunner.ActionResult, error) {
|
func (h *TickParentHandler) Execute(ctx context.Context, signal *jobrunner.PipelineSignal) (*jobrunner.ActionResult, error) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ func TestTickParent_Match_Good(t *testing.T) {
|
||||||
assert.True(t, h.Match(sig))
|
assert.True(t, h.Match(sig))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTickParent_Match_Bad_Open(t *testing.T) {
|
func TestTickParent_Match_Bad_Open_Good(t *testing.T) {
|
||||||
h := NewTickParentHandler(nil)
|
h := NewTickParentHandler(nil)
|
||||||
sig := &jobrunner.PipelineSignal{
|
sig := &jobrunner.PipelineSignal{
|
||||||
PRState: "OPEN",
|
PRState: "OPEN",
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@ func TestJournal_Append_Good(t *testing.T) {
|
||||||
assert.Equal(t, 2, lines, "expected two JSONL lines after two appends")
|
assert.Equal(t, 2, lines, "expected two JSONL lines after two appends")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJournal_Append_Bad_PathTraversal(t *testing.T) {
|
func TestJournal_Append_Bad_PathTraversal_Good(t *testing.T) {
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
|
|
||||||
j, err := NewJournal(dir)
|
j, err := NewJournal(dir)
|
||||||
|
|
@ -197,7 +197,7 @@ func TestJournal_Append_Bad_PathTraversal(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJournal_Append_Good_ValidNames(t *testing.T) {
|
func TestJournal_Append_Good_ValidNames_Good(t *testing.T) {
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
|
|
||||||
j, err := NewJournal(dir)
|
j, err := NewJournal(dir)
|
||||||
|
|
@ -232,7 +232,7 @@ func TestJournal_Append_Good_ValidNames(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJournal_Append_Bad_NilSignal(t *testing.T) {
|
func TestJournal_Append_Bad_NilSignal_Good(t *testing.T) {
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
|
|
||||||
j, err := NewJournal(dir)
|
j, err := NewJournal(dir)
|
||||||
|
|
@ -248,7 +248,7 @@ func TestJournal_Append_Bad_NilSignal(t *testing.T) {
|
||||||
assert.Contains(t, err.Error(), "signal is required")
|
assert.Contains(t, err.Error(), "signal is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJournal_Append_Bad_NilResult(t *testing.T) {
|
func TestJournal_Append_Bad_NilResult_Good(t *testing.T) {
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
|
|
||||||
j, err := NewJournal(dir)
|
j, err := NewJournal(dir)
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,7 @@ func TestPoller_RunOnce_Good(t *testing.T) {
|
||||||
assert.Equal(t, 1, p.Cycle())
|
assert.Equal(t, 1, p.Cycle())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPoller_RunOnce_Good_NoSignals(t *testing.T) {
|
func TestPoller_RunOnce_Good_NoSignals_Good(t *testing.T) {
|
||||||
src := &mockSource{
|
src := &mockSource{
|
||||||
name: "empty-source",
|
name: "empty-source",
|
||||||
signals: nil,
|
signals: nil,
|
||||||
|
|
@ -151,7 +151,7 @@ func TestPoller_RunOnce_Good_NoSignals(t *testing.T) {
|
||||||
assert.Equal(t, 1, p.Cycle())
|
assert.Equal(t, 1, p.Cycle())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPoller_RunOnce_Good_NoMatchingHandler(t *testing.T) {
|
func TestPoller_RunOnce_Good_NoMatchingHandler_Good(t *testing.T) {
|
||||||
sig := &PipelineSignal{
|
sig := &PipelineSignal{
|
||||||
EpicNumber: 5,
|
EpicNumber: 5,
|
||||||
ChildNumber: 8,
|
ChildNumber: 8,
|
||||||
|
|
@ -192,7 +192,7 @@ func TestPoller_RunOnce_Good_NoMatchingHandler(t *testing.T) {
|
||||||
assert.Empty(t, src.reports)
|
assert.Empty(t, src.reports)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPoller_RunOnce_Good_DryRun(t *testing.T) {
|
func TestPoller_RunOnce_Good_DryRun_Good(t *testing.T) {
|
||||||
sig := &PipelineSignal{
|
sig := &PipelineSignal{
|
||||||
EpicNumber: 1,
|
EpicNumber: 1,
|
||||||
ChildNumber: 3,
|
ChildNumber: 3,
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ func TestPipelineSignal_HasUnresolvedThreads_Good(t *testing.T) {
|
||||||
assert.True(t, sig.HasUnresolvedThreads())
|
assert.True(t, sig.HasUnresolvedThreads())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPipelineSignal_HasUnresolvedThreads_Bad_AllResolved(t *testing.T) {
|
func TestPipelineSignal_HasUnresolvedThreads_Bad_AllResolved_Good(t *testing.T) {
|
||||||
sig := &PipelineSignal{
|
sig := &PipelineSignal{
|
||||||
ThreadsTotal: 4,
|
ThreadsTotal: 4,
|
||||||
ThreadsResolved: 4,
|
ThreadsResolved: 4,
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ func TestCompile_Good(t *testing.T) {
|
||||||
assert.NotEmpty(t, cm.BuiltAt)
|
assert.NotEmpty(t, cm.BuiltAt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCompile_Good_WithSigning(t *testing.T) {
|
func TestCompile_Good_WithSigning_Good(t *testing.T) {
|
||||||
pub, priv, err := ed25519.GenerateKey(rand.Reader)
|
pub, priv, err := ed25519.GenerateKey(rand.Reader)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
|
@ -60,20 +60,20 @@ func TestCompile_Good_WithSigning(t *testing.T) {
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCompile_Bad_NilManifest(t *testing.T) {
|
func TestCompile_Bad_NilManifest_Good(t *testing.T) {
|
||||||
_, err := Compile(nil, CompileOptions{})
|
_, err := Compile(nil, CompileOptions{})
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Contains(t, err.Error(), "nil manifest")
|
assert.Contains(t, err.Error(), "nil manifest")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCompile_Bad_MissingCode(t *testing.T) {
|
func TestCompile_Bad_MissingCode_Good(t *testing.T) {
|
||||||
m := &Manifest{Version: "1.0.0"}
|
m := &Manifest{Version: "1.0.0"}
|
||||||
_, err := Compile(m, CompileOptions{})
|
_, err := Compile(m, CompileOptions{})
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Contains(t, err.Error(), "missing code")
|
assert.Contains(t, err.Error(), "missing code")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCompile_Bad_MissingVersion(t *testing.T) {
|
func TestCompile_Bad_MissingVersion_Good(t *testing.T) {
|
||||||
m := &Manifest{Code: "test"}
|
m := &Manifest{Code: "test"}
|
||||||
_, err := Compile(m, CompileOptions{})
|
_, err := Compile(m, CompileOptions{})
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
|
|
@ -162,13 +162,13 @@ func TestLoadCompiled_Good(t *testing.T) {
|
||||||
assert.Equal(t, "ddd444", cm.Commit)
|
assert.Equal(t, "ddd444", cm.Commit)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadCompiled_Bad_NotFound(t *testing.T) {
|
func TestLoadCompiled_Bad_NotFound_Good(t *testing.T) {
|
||||||
medium := io.NewMockMedium()
|
medium := io.NewMockMedium()
|
||||||
_, err := LoadCompiled(medium, "/missing")
|
_, err := LoadCompiled(medium, "/missing")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCompile_Good_MinimalOptions(t *testing.T) {
|
func TestCompile_Good_MinimalOptions_Good(t *testing.T) {
|
||||||
m := &Manifest{
|
m := &Manifest{
|
||||||
Code: "minimal",
|
Code: "minimal",
|
||||||
Name: "Minimal",
|
Name: "Minimal",
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ slots:
|
||||||
assert.Equal(t, "main-content", m.Slots["C"])
|
assert.Equal(t, "main-content", m.Slots["C"])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoad_Bad_NoManifest(t *testing.T) {
|
func TestLoad_Bad_NoManifest_Good(t *testing.T) {
|
||||||
fs := io.NewMockMedium()
|
fs := io.NewMockMedium()
|
||||||
_, err := Load(fs, ".")
|
_, err := Load(fs, ".")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
|
|
@ -50,7 +50,7 @@ func TestLoadVerified_Good(t *testing.T) {
|
||||||
assert.Equal(t, "signed-app", loaded.Code)
|
assert.Equal(t, "signed-app", loaded.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadVerified_Bad_Tampered(t *testing.T) {
|
func TestLoadVerified_Bad_Tampered_Good(t *testing.T) {
|
||||||
pub, priv, _ := ed25519.GenerateKey(nil)
|
pub, priv, _ := ed25519.GenerateKey(nil)
|
||||||
m := &Manifest{Code: "app", Version: "1.0.0"}
|
m := &Manifest{Code: "app", Version: "1.0.0"}
|
||||||
_ = Sign(m, priv)
|
_ = Sign(m, priv)
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ type ElementSpec struct {
|
||||||
|
|
||||||
// IsProvider returns true if this manifest declares provider fields
|
// IsProvider returns true if this manifest declares provider fields
|
||||||
// (namespace and binary), indicating it is a runtime provider.
|
// (namespace and binary), indicating it is a runtime provider.
|
||||||
|
// Usage: IsProvider(...)
|
||||||
func (m *Manifest) IsProvider() bool {
|
func (m *Manifest) IsProvider() bool {
|
||||||
return m.Namespace != "" && m.Binary != ""
|
return m.Namespace != "" && m.Binary != ""
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ func TestManifest_SlotNames_Good(t *testing.T) {
|
||||||
assert.Len(t, names, 2)
|
assert.Len(t, names, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParse_Good_WithDaemons(t *testing.T) {
|
func TestParse_Good_WithDaemons_Good(t *testing.T) {
|
||||||
raw := `
|
raw := `
|
||||||
code: my-service
|
code: my-service
|
||||||
name: My Service
|
name: My Service
|
||||||
|
|
@ -126,7 +126,7 @@ func TestManifest_DefaultDaemon_Good(t *testing.T) {
|
||||||
assert.True(t, spec.Default)
|
assert.True(t, spec.Default)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestManifest_DefaultDaemon_Bad_NoDaemons(t *testing.T) {
|
func TestManifest_DefaultDaemon_Bad_NoDaemons_Good(t *testing.T) {
|
||||||
m := Manifest{}
|
m := Manifest{}
|
||||||
name, spec, ok := m.DefaultDaemon()
|
name, spec, ok := m.DefaultDaemon()
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
|
|
@ -134,7 +134,7 @@ func TestManifest_DefaultDaemon_Bad_NoDaemons(t *testing.T) {
|
||||||
assert.Empty(t, spec.Binary)
|
assert.Empty(t, spec.Binary)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestManifest_DefaultDaemon_Bad_MultipleDefaults(t *testing.T) {
|
func TestManifest_DefaultDaemon_Bad_MultipleDefaults_Good(t *testing.T) {
|
||||||
m := Manifest{
|
m := Manifest{
|
||||||
Daemons: map[string]DaemonSpec{
|
Daemons: map[string]DaemonSpec{
|
||||||
"api": {Binary: "./bin/api", Default: true},
|
"api": {Binary: "./bin/api", Default: true},
|
||||||
|
|
@ -145,7 +145,7 @@ func TestManifest_DefaultDaemon_Bad_MultipleDefaults(t *testing.T) {
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestManifest_DefaultDaemon_Bad_MultipleNoneDefault(t *testing.T) {
|
func TestManifest_DefaultDaemon_Bad_MultipleNoneDefault_Good(t *testing.T) {
|
||||||
m := Manifest{
|
m := Manifest{
|
||||||
Daemons: map[string]DaemonSpec{
|
Daemons: map[string]DaemonSpec{
|
||||||
"api": {Binary: "./bin/api"},
|
"api": {Binary: "./bin/api"},
|
||||||
|
|
@ -156,7 +156,7 @@ func TestManifest_DefaultDaemon_Bad_MultipleNoneDefault(t *testing.T) {
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParse_Good_WithProviderFields(t *testing.T) {
|
func TestParse_Good_WithProviderFields_Good(t *testing.T) {
|
||||||
raw := `
|
raw := `
|
||||||
code: cool-widget
|
code: cool-widget
|
||||||
name: Cool Widget Dashboard
|
name: Cool Widget Dashboard
|
||||||
|
|
@ -204,22 +204,22 @@ func TestManifest_IsProvider_Good(t *testing.T) {
|
||||||
assert.True(t, m.IsProvider())
|
assert.True(t, m.IsProvider())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestManifest_IsProvider_Bad_NoNamespace(t *testing.T) {
|
func TestManifest_IsProvider_Bad_NoNamespace_Good(t *testing.T) {
|
||||||
m := Manifest{Binary: "./test"}
|
m := Manifest{Binary: "./test"}
|
||||||
assert.False(t, m.IsProvider())
|
assert.False(t, m.IsProvider())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestManifest_IsProvider_Bad_NoBinary(t *testing.T) {
|
func TestManifest_IsProvider_Bad_NoBinary_Good(t *testing.T) {
|
||||||
m := Manifest{Namespace: "/api/v1/test"}
|
m := Manifest{Namespace: "/api/v1/test"}
|
||||||
assert.False(t, m.IsProvider())
|
assert.False(t, m.IsProvider())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestManifest_IsProvider_Bad_Empty(t *testing.T) {
|
func TestManifest_IsProvider_Bad_Empty_Good(t *testing.T) {
|
||||||
m := Manifest{}
|
m := Manifest{}
|
||||||
assert.False(t, m.IsProvider())
|
assert.False(t, m.IsProvider())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestManifest_DefaultDaemon_Good_SingleImplicit(t *testing.T) {
|
func TestManifest_DefaultDaemon_Good_SingleImplicit_Good(t *testing.T) {
|
||||||
m := Manifest{
|
m := Manifest{
|
||||||
Daemons: map[string]DaemonSpec{
|
Daemons: map[string]DaemonSpec{
|
||||||
"server": {
|
"server": {
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ func TestSignAndVerify_Good(t *testing.T) {
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVerify_Bad_Tampered(t *testing.T) {
|
func TestVerify_Bad_Tampered_Good(t *testing.T) {
|
||||||
pub, priv, _ := ed25519.GenerateKey(nil)
|
pub, priv, _ := ed25519.GenerateKey(nil)
|
||||||
m := &Manifest{Code: "test-app", Version: "1.0.0"}
|
m := &Manifest{Code: "test-app", Version: "1.0.0"}
|
||||||
_ = Sign(m, priv)
|
_ = Sign(m, priv)
|
||||||
|
|
@ -43,7 +43,7 @@ func TestVerify_Bad_Tampered(t *testing.T) {
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVerify_Bad_Unsigned(t *testing.T) {
|
func TestVerify_Bad_Unsigned_Good(t *testing.T) {
|
||||||
pub, _, _ := ed25519.GenerateKey(nil)
|
pub, _, _ := ed25519.GenerateKey(nil)
|
||||||
m := &Manifest{Code: "test-app"}
|
m := &Manifest{Code: "test-app"}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ func writeCoreJSON(t *testing.T, dir, code, name, version string) {
|
||||||
require.NoError(t, os.WriteFile(filepath.Join(dir, "core.json"), data, 0644))
|
require.NoError(t, os.WriteFile(filepath.Join(dir, "core.json"), data, 0644))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildFromDirs_Good_ManifestYAML(t *testing.T) {
|
func TestBuildFromDirs_Good_ManifestYAML_Good(t *testing.T) {
|
||||||
root := t.TempDir()
|
root := t.TempDir()
|
||||||
modDir := filepath.Join(root, "my-widget")
|
modDir := filepath.Join(root, "my-widget")
|
||||||
require.NoError(t, os.MkdirAll(modDir, 0755))
|
require.NoError(t, os.MkdirAll(modDir, 0755))
|
||||||
|
|
@ -55,7 +55,7 @@ func TestBuildFromDirs_Good_ManifestYAML(t *testing.T) {
|
||||||
assert.Equal(t, IndexVersion, idx.Version)
|
assert.Equal(t, IndexVersion, idx.Version)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildFromDirs_Good_CoreJSON(t *testing.T) {
|
func TestBuildFromDirs_Good_CoreJSON_Good(t *testing.T) {
|
||||||
root := t.TempDir()
|
root := t.TempDir()
|
||||||
modDir := filepath.Join(root, "compiled-mod")
|
modDir := filepath.Join(root, "compiled-mod")
|
||||||
require.NoError(t, os.MkdirAll(modDir, 0755))
|
require.NoError(t, os.MkdirAll(modDir, 0755))
|
||||||
|
|
@ -70,7 +70,7 @@ func TestBuildFromDirs_Good_CoreJSON(t *testing.T) {
|
||||||
assert.Equal(t, "Compiled Module", idx.Modules[0].Name)
|
assert.Equal(t, "Compiled Module", idx.Modules[0].Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildFromDirs_Good_PrefersCompiledOverSource(t *testing.T) {
|
func TestBuildFromDirs_Good_PrefersCompiledOverSource_Good(t *testing.T) {
|
||||||
root := t.TempDir()
|
root := t.TempDir()
|
||||||
modDir := filepath.Join(root, "dual-mod")
|
modDir := filepath.Join(root, "dual-mod")
|
||||||
require.NoError(t, os.MkdirAll(modDir, 0755))
|
require.NoError(t, os.MkdirAll(modDir, 0755))
|
||||||
|
|
@ -86,7 +86,7 @@ func TestBuildFromDirs_Good_PrefersCompiledOverSource(t *testing.T) {
|
||||||
assert.Equal(t, "compiled-code", idx.Modules[0].Code)
|
assert.Equal(t, "compiled-code", idx.Modules[0].Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildFromDirs_Good_SkipsNoManifest(t *testing.T) {
|
func TestBuildFromDirs_Good_SkipsNoManifest_Good(t *testing.T) {
|
||||||
root := t.TempDir()
|
root := t.TempDir()
|
||||||
// Directory with no manifest.
|
// Directory with no manifest.
|
||||||
require.NoError(t, os.MkdirAll(filepath.Join(root, "no-manifest"), 0755))
|
require.NoError(t, os.MkdirAll(filepath.Join(root, "no-manifest"), 0755))
|
||||||
|
|
@ -101,7 +101,7 @@ func TestBuildFromDirs_Good_SkipsNoManifest(t *testing.T) {
|
||||||
assert.Len(t, idx.Modules, 1)
|
assert.Len(t, idx.Modules, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildFromDirs_Good_Deduplicates(t *testing.T) {
|
func TestBuildFromDirs_Good_Deduplicates_Good(t *testing.T) {
|
||||||
dir1 := t.TempDir()
|
dir1 := t.TempDir()
|
||||||
dir2 := t.TempDir()
|
dir2 := t.TempDir()
|
||||||
|
|
||||||
|
|
@ -120,7 +120,7 @@ func TestBuildFromDirs_Good_Deduplicates(t *testing.T) {
|
||||||
assert.Equal(t, "shared", idx.Modules[0].Code)
|
assert.Equal(t, "shared", idx.Modules[0].Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildFromDirs_Good_SortsByCode(t *testing.T) {
|
func TestBuildFromDirs_Good_SortsByCode_Good(t *testing.T) {
|
||||||
root := t.TempDir()
|
root := t.TempDir()
|
||||||
for _, name := range []string{"charlie", "alpha", "bravo"} {
|
for _, name := range []string{"charlie", "alpha", "bravo"} {
|
||||||
d := filepath.Join(root, name)
|
d := filepath.Join(root, name)
|
||||||
|
|
@ -137,7 +137,7 @@ func TestBuildFromDirs_Good_SortsByCode(t *testing.T) {
|
||||||
assert.Equal(t, "charlie", idx.Modules[2].Code)
|
assert.Equal(t, "charlie", idx.Modules[2].Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildFromDirs_Good_EmptyDir(t *testing.T) {
|
func TestBuildFromDirs_Good_EmptyDir_Good(t *testing.T) {
|
||||||
root := t.TempDir()
|
root := t.TempDir()
|
||||||
b := &Builder{}
|
b := &Builder{}
|
||||||
idx, err := b.BuildFromDirs(root)
|
idx, err := b.BuildFromDirs(root)
|
||||||
|
|
@ -146,14 +146,14 @@ func TestBuildFromDirs_Good_EmptyDir(t *testing.T) {
|
||||||
assert.Equal(t, IndexVersion, idx.Version)
|
assert.Equal(t, IndexVersion, idx.Version)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildFromDirs_Good_NonexistentDir(t *testing.T) {
|
func TestBuildFromDirs_Good_NonexistentDir_Good(t *testing.T) {
|
||||||
b := &Builder{}
|
b := &Builder{}
|
||||||
idx, err := b.BuildFromDirs("/nonexistent/path")
|
idx, err := b.BuildFromDirs("/nonexistent/path")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Empty(t, idx.Modules)
|
assert.Empty(t, idx.Modules)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildFromDirs_Good_NoRepoURLWithoutConfig(t *testing.T) {
|
func TestBuildFromDirs_Good_NoRepoURLWithoutConfig_Good(t *testing.T) {
|
||||||
root := t.TempDir()
|
root := t.TempDir()
|
||||||
modDir := filepath.Join(root, "mod")
|
modDir := filepath.Join(root, "mod")
|
||||||
require.NoError(t, os.MkdirAll(modDir, 0755))
|
require.NoError(t, os.MkdirAll(modDir, 0755))
|
||||||
|
|
@ -177,7 +177,7 @@ func TestBuildFromManifests_Good(t *testing.T) {
|
||||||
assert.Equal(t, IndexVersion, idx.Version)
|
assert.Equal(t, IndexVersion, idx.Version)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildFromManifests_Good_SkipsNil(t *testing.T) {
|
func TestBuildFromManifests_Good_SkipsNil_Good(t *testing.T) {
|
||||||
manifests := []*manifest.Manifest{
|
manifests := []*manifest.Manifest{
|
||||||
nil,
|
nil,
|
||||||
{Code: "valid", Name: "Valid"},
|
{Code: "valid", Name: "Valid"},
|
||||||
|
|
@ -188,7 +188,7 @@ func TestBuildFromManifests_Good_SkipsNil(t *testing.T) {
|
||||||
assert.Equal(t, "valid", idx.Modules[0].Code)
|
assert.Equal(t, "valid", idx.Modules[0].Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildFromManifests_Good_Deduplicates(t *testing.T) {
|
func TestBuildFromManifests_Good_Deduplicates_Good(t *testing.T) {
|
||||||
manifests := []*manifest.Manifest{
|
manifests := []*manifest.Manifest{
|
||||||
{Code: "dup", Name: "First"},
|
{Code: "dup", Name: "First"},
|
||||||
{Code: "dup", Name: "Second"},
|
{Code: "dup", Name: "Second"},
|
||||||
|
|
@ -220,7 +220,7 @@ func TestWriteIndex_Good(t *testing.T) {
|
||||||
assert.Equal(t, "test-mod", parsed.Modules[0].Code)
|
assert.Equal(t, "test-mod", parsed.Modules[0].Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWriteIndex_Good_RoundTrip(t *testing.T) {
|
func TestWriteIndex_Good_RoundTrip_Good(t *testing.T) {
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
path := filepath.Join(dir, "index.json")
|
path := filepath.Join(dir, "index.json")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ binary: ./data-viz
|
||||||
assert.True(t, codes["data-viz"])
|
assert.True(t, codes["data-viz"])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDiscoverProviders_Good_SkipNonProvider(t *testing.T) {
|
func TestDiscoverProviders_Good_SkipNonProvider_Good(t *testing.T) {
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
|
|
||||||
// This has a valid manifest but no namespace/binary — not a provider.
|
// This has a valid manifest but no namespace/binary — not a provider.
|
||||||
|
|
@ -83,7 +83,7 @@ binary: ./real-provider
|
||||||
assert.Equal(t, "real-provider", providers[0].Manifest.Code)
|
assert.Equal(t, "real-provider", providers[0].Manifest.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDiscoverProviders_Good_SkipNoManifest(t *testing.T) {
|
func TestDiscoverProviders_Good_SkipNoManifest_Good(t *testing.T) {
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
|
|
||||||
// Directory with no manifest.
|
// Directory with no manifest.
|
||||||
|
|
@ -104,7 +104,7 @@ binary: ./good-provider
|
||||||
assert.Equal(t, "good-provider", providers[0].Manifest.Code)
|
assert.Equal(t, "good-provider", providers[0].Manifest.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDiscoverProviders_Good_SkipInvalidManifest(t *testing.T) {
|
func TestDiscoverProviders_Good_SkipInvalidManifest_Good(t *testing.T) {
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
|
|
||||||
// Directory with invalid YAML.
|
// Directory with invalid YAML.
|
||||||
|
|
@ -121,7 +121,7 @@ func TestDiscoverProviders_Good_SkipInvalidManifest(t *testing.T) {
|
||||||
assert.Empty(t, providers)
|
assert.Empty(t, providers)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDiscoverProviders_Good_EmptyDir(t *testing.T) {
|
func TestDiscoverProviders_Good_EmptyDir_Good(t *testing.T) {
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
|
|
||||||
providers, err := DiscoverProviders(dir)
|
providers, err := DiscoverProviders(dir)
|
||||||
|
|
@ -129,13 +129,13 @@ func TestDiscoverProviders_Good_EmptyDir(t *testing.T) {
|
||||||
assert.Empty(t, providers)
|
assert.Empty(t, providers)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDiscoverProviders_Good_NonexistentDir(t *testing.T) {
|
func TestDiscoverProviders_Good_NonexistentDir_Good(t *testing.T) {
|
||||||
providers, err := DiscoverProviders("/tmp/nonexistent-discovery-test-dir")
|
providers, err := DiscoverProviders("/tmp/nonexistent-discovery-test-dir")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Nil(t, providers)
|
assert.Nil(t, providers)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDiscoverProviders_Good_SkipFiles(t *testing.T) {
|
func TestDiscoverProviders_Good_SkipFiles_Good(t *testing.T) {
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
|
|
||||||
// Create a regular file (not a directory).
|
// Create a regular file (not a directory).
|
||||||
|
|
@ -146,7 +146,7 @@ func TestDiscoverProviders_Good_SkipFiles(t *testing.T) {
|
||||||
assert.Empty(t, providers)
|
assert.Empty(t, providers)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDiscoverProviders_Good_ProviderDir(t *testing.T) {
|
func TestDiscoverProviders_Good_ProviderDir_Good(t *testing.T) {
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
|
|
||||||
createProviderDir(t, dir, "test-prov", `
|
createProviderDir(t, dir, "test-prov", `
|
||||||
|
|
@ -194,7 +194,7 @@ func TestProviderRegistry_LoadSave_Good(t *testing.T) {
|
||||||
assert.True(t, entry.AutoStart)
|
assert.True(t, entry.AutoStart)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProviderRegistry_Load_Good_NonexistentFile(t *testing.T) {
|
func TestProviderRegistry_Load_Good_NonexistentFile_Good(t *testing.T) {
|
||||||
reg, err := LoadProviderRegistry("/tmp/nonexistent-registry-test.yaml")
|
reg, err := LoadProviderRegistry("/tmp/nonexistent-registry-test.yaml")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, 1, reg.Version)
|
assert.Equal(t, 1, reg.Version)
|
||||||
|
|
@ -233,7 +233,7 @@ func TestProviderRegistry_Remove_Good(t *testing.T) {
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProviderRegistry_Get_Bad_NotFound(t *testing.T) {
|
func TestProviderRegistry_Get_Bad_NotFound_Good(t *testing.T) {
|
||||||
reg := &ProviderRegistryFile{
|
reg := &ProviderRegistryFile{
|
||||||
Version: 1,
|
Version: 1,
|
||||||
Providers: map[string]ProviderRegistryEntry{},
|
Providers: map[string]ProviderRegistryEntry{},
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ func TestInstall_Good(t *testing.T) {
|
||||||
assert.Contains(t, raw, `"version":"1.0"`)
|
assert.Contains(t, raw, `"version":"1.0"`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInstall_Good_Signed(t *testing.T) {
|
func TestInstall_Good_Signed_Good(t *testing.T) {
|
||||||
repo, signKey := createSignedTestRepo(t, "signed-mod", "2.0")
|
repo, signKey := createSignedTestRepo(t, "signed-mod", "2.0")
|
||||||
modulesDir := filepath.Join(t.TempDir(), "modules")
|
modulesDir := filepath.Join(t.TempDir(), "modules")
|
||||||
|
|
||||||
|
|
@ -124,7 +124,7 @@ func TestInstall_Good_Signed(t *testing.T) {
|
||||||
assert.Contains(t, raw, `"version":"2.0"`)
|
assert.Contains(t, raw, `"version":"2.0"`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInstall_Bad_AlreadyInstalled(t *testing.T) {
|
func TestInstall_Bad_AlreadyInstalled_Good(t *testing.T) {
|
||||||
repo := createTestRepo(t, "dup-mod", "1.0")
|
repo := createTestRepo(t, "dup-mod", "1.0")
|
||||||
modulesDir := filepath.Join(t.TempDir(), "modules")
|
modulesDir := filepath.Join(t.TempDir(), "modules")
|
||||||
|
|
||||||
|
|
@ -141,7 +141,7 @@ func TestInstall_Bad_AlreadyInstalled(t *testing.T) {
|
||||||
assert.Contains(t, err.Error(), "already installed")
|
assert.Contains(t, err.Error(), "already installed")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInstall_Bad_InvalidSignature(t *testing.T) {
|
func TestInstall_Bad_InvalidSignature_Good(t *testing.T) {
|
||||||
// Sign with key A, verify with key B
|
// Sign with key A, verify with key B
|
||||||
repo, _ := createSignedTestRepo(t, "bad-sig", "1.0")
|
repo, _ := createSignedTestRepo(t, "bad-sig", "1.0")
|
||||||
_, wrongKey := createSignedTestRepo(t, "dummy", "1.0") // different key
|
_, wrongKey := createSignedTestRepo(t, "dummy", "1.0") // different key
|
||||||
|
|
@ -165,7 +165,7 @@ func TestInstall_Bad_InvalidSignature(t *testing.T) {
|
||||||
assert.True(t, os.IsNotExist(statErr), "directory should be cleaned up on failure")
|
assert.True(t, os.IsNotExist(statErr), "directory should be cleaned up on failure")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInstall_Bad_PathTraversalCode(t *testing.T) {
|
func TestInstall_Bad_PathTraversalCode_Good(t *testing.T) {
|
||||||
repo := createTestRepo(t, "safe-mod", "1.0")
|
repo := createTestRepo(t, "safe-mod", "1.0")
|
||||||
modulesDir := filepath.Join(t.TempDir(), "modules")
|
modulesDir := filepath.Join(t.TempDir(), "modules")
|
||||||
|
|
||||||
|
|
@ -211,7 +211,7 @@ func TestRemove_Good(t *testing.T) {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRemove_Bad_NotInstalled(t *testing.T) {
|
func TestRemove_Bad_NotInstalled_Good(t *testing.T) {
|
||||||
st, err := store.New(":memory:")
|
st, err := store.New(":memory:")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer st.Close()
|
defer st.Close()
|
||||||
|
|
@ -222,7 +222,7 @@ func TestRemove_Bad_NotInstalled(t *testing.T) {
|
||||||
assert.Contains(t, err.Error(), "not installed")
|
assert.Contains(t, err.Error(), "not installed")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRemove_Bad_PathTraversalCode(t *testing.T) {
|
func TestRemove_Bad_PathTraversalCode_Good(t *testing.T) {
|
||||||
baseDir := t.TempDir()
|
baseDir := t.TempDir()
|
||||||
modulesDir := filepath.Join(baseDir, "modules")
|
modulesDir := filepath.Join(baseDir, "modules")
|
||||||
escapeDir := filepath.Join(baseDir, "escape")
|
escapeDir := filepath.Join(baseDir, "escape")
|
||||||
|
|
@ -269,7 +269,7 @@ func TestInstalled_Good(t *testing.T) {
|
||||||
assert.True(t, codes["mod-b"])
|
assert.True(t, codes["mod-b"])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInstalled_Good_Empty(t *testing.T) {
|
func TestInstalled_Good_Empty_Good(t *testing.T) {
|
||||||
st, err := store.New(":memory:")
|
st, err := store.New(":memory:")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer st.Close()
|
defer st.Close()
|
||||||
|
|
@ -308,7 +308,7 @@ func TestUpdate_Good(t *testing.T) {
|
||||||
assert.Equal(t, "Updated Module", installed[0].Name)
|
assert.Equal(t, "Updated Module", installed[0].Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdate_Bad_PathTraversalCode(t *testing.T) {
|
func TestUpdate_Bad_PathTraversalCode_Good(t *testing.T) {
|
||||||
modulesDir := filepath.Join(t.TempDir(), "modules")
|
modulesDir := filepath.Join(t.TempDir(), "modules")
|
||||||
|
|
||||||
st, err := store.New(":memory:")
|
st, err := store.New(":memory:")
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ func TestFind_Good(t *testing.T) {
|
||||||
assert.Equal(t, "XMRig", m.Name)
|
assert.Equal(t, "XMRig", m.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFind_Bad_NotFound(t *testing.T) {
|
func TestFind_Bad_NotFound_Good(t *testing.T) {
|
||||||
idx := &Index{}
|
idx := &Index{}
|
||||||
_, ok := idx.Find("nope")
|
_, ok := idx.Find("nope")
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
|
|
|
||||||
|
|
@ -164,7 +164,7 @@ func TestScmProvider_GetMarketplaceItem_Bad(t *testing.T) {
|
||||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestScmProvider_GetMarketplaceItem_Bad_PathTraversal(t *testing.T) {
|
func TestScmProvider_GetMarketplaceItem_Bad_PathTraversal_Good(t *testing.T) {
|
||||||
idx := &marketplace.Index{Version: 1}
|
idx := &marketplace.Index{Version: 1}
|
||||||
p := scmapi.NewProvider(idx, nil, nil, nil)
|
p := scmapi.NewProvider(idx, nil, nil, nil)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ func NewInstaller(m io.Medium, registry *Registry) *Installer {
|
||||||
|
|
||||||
// Install downloads and installs a plugin from GitHub.
|
// Install downloads and installs a plugin from GitHub.
|
||||||
// The source format is "org/repo" or "org/repo@version".
|
// The source format is "org/repo" or "org/repo@version".
|
||||||
|
// Usage: Install(...)
|
||||||
func (i *Installer) Install(ctx context.Context, source string) error {
|
func (i *Installer) Install(ctx context.Context, source string) error {
|
||||||
org, repo, version, err := ParseSource(source)
|
org, repo, version, err := ParseSource(source)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -96,6 +97,7 @@ func (i *Installer) Install(ctx context.Context, source string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update updates a plugin to the latest version.
|
// Update updates a plugin to the latest version.
|
||||||
|
// Usage: Update(...)
|
||||||
func (i *Installer) Update(ctx context.Context, name string) error {
|
func (i *Installer) Update(ctx context.Context, name string) error {
|
||||||
safeName, pluginDir, err := i.resolvePluginPath(name)
|
safeName, pluginDir, err := i.resolvePluginPath(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -130,6 +132,7 @@ func (i *Installer) Update(ctx context.Context, name string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove uninstalls a plugin by removing its files and registry entry.
|
// Remove uninstalls a plugin by removing its files and registry entry.
|
||||||
|
// Usage: Remove(...)
|
||||||
func (i *Installer) Remove(name string) error {
|
func (i *Installer) Remove(name string) error {
|
||||||
safeName, pluginDir, err := i.resolvePluginPath(name)
|
safeName, pluginDir, err := i.resolvePluginPath(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ func TestNewInstaller_Good(t *testing.T) {
|
||||||
|
|
||||||
// ── Install error paths ────────────────────────────────────────────
|
// ── Install error paths ────────────────────────────────────────────
|
||||||
|
|
||||||
func TestInstall_Bad_InvalidSource(t *testing.T) {
|
func TestInstall_Bad_InvalidSource_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
reg := NewRegistry(m, "/plugins")
|
reg := NewRegistry(m, "/plugins")
|
||||||
inst := NewInstaller(m, reg)
|
inst := NewInstaller(m, reg)
|
||||||
|
|
@ -35,7 +35,7 @@ func TestInstall_Bad_InvalidSource(t *testing.T) {
|
||||||
assert.Contains(t, err.Error(), "invalid source")
|
assert.Contains(t, err.Error(), "invalid source")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInstall_Bad_AlreadyInstalled(t *testing.T) {
|
func TestInstall_Bad_AlreadyInstalled_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
reg := NewRegistry(m, "/plugins")
|
reg := NewRegistry(m, "/plugins")
|
||||||
_ = reg.Add(&PluginConfig{Name: "my-plugin", Version: "1.0.0"})
|
_ = reg.Add(&PluginConfig{Name: "my-plugin", Version: "1.0.0"})
|
||||||
|
|
@ -69,7 +69,7 @@ func TestRemove_Good(t *testing.T) {
|
||||||
assert.False(t, m.Exists("/plugins/removable"))
|
assert.False(t, m.Exists("/plugins/removable"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRemove_Good_DirAlreadyGone(t *testing.T) {
|
func TestRemove_Good_DirAlreadyGone_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
reg := NewRegistry(m, "/plugins")
|
reg := NewRegistry(m, "/plugins")
|
||||||
_ = reg.Add(&PluginConfig{Name: "ghost", Version: "1.0.0"})
|
_ = reg.Add(&PluginConfig{Name: "ghost", Version: "1.0.0"})
|
||||||
|
|
@ -83,7 +83,7 @@ func TestRemove_Good_DirAlreadyGone(t *testing.T) {
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRemove_Bad_NotFound(t *testing.T) {
|
func TestRemove_Bad_NotFound_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
reg := NewRegistry(m, "/plugins")
|
reg := NewRegistry(m, "/plugins")
|
||||||
inst := NewInstaller(m, reg)
|
inst := NewInstaller(m, reg)
|
||||||
|
|
@ -95,7 +95,7 @@ func TestRemove_Bad_NotFound(t *testing.T) {
|
||||||
|
|
||||||
// ── Update error paths ─────────────────────────────────────────────
|
// ── Update error paths ─────────────────────────────────────────────
|
||||||
|
|
||||||
func TestUpdate_Bad_NotFound(t *testing.T) {
|
func TestUpdate_Bad_NotFound_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
reg := NewRegistry(m, "/plugins")
|
reg := NewRegistry(m, "/plugins")
|
||||||
inst := NewInstaller(m, reg)
|
inst := NewInstaller(m, reg)
|
||||||
|
|
@ -107,7 +107,7 @@ func TestUpdate_Bad_NotFound(t *testing.T) {
|
||||||
|
|
||||||
// ── ParseSource ────────────────────────────────────────────────────
|
// ── ParseSource ────────────────────────────────────────────────────
|
||||||
|
|
||||||
func TestParseSource_Good_OrgRepo(t *testing.T) {
|
func TestParseSource_Good_OrgRepo_Good(t *testing.T) {
|
||||||
org, repo, version, err := ParseSource("host-uk/core-plugin")
|
org, repo, version, err := ParseSource("host-uk/core-plugin")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "host-uk", org)
|
assert.Equal(t, "host-uk", org)
|
||||||
|
|
@ -115,7 +115,7 @@ func TestParseSource_Good_OrgRepo(t *testing.T) {
|
||||||
assert.Equal(t, "", version)
|
assert.Equal(t, "", version)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseSource_Good_OrgRepoVersion(t *testing.T) {
|
func TestParseSource_Good_OrgRepoVersion_Good(t *testing.T) {
|
||||||
org, repo, version, err := ParseSource("host-uk/core-plugin@v1.0.0")
|
org, repo, version, err := ParseSource("host-uk/core-plugin@v1.0.0")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "host-uk", org)
|
assert.Equal(t, "host-uk", org)
|
||||||
|
|
@ -123,7 +123,7 @@ func TestParseSource_Good_OrgRepoVersion(t *testing.T) {
|
||||||
assert.Equal(t, "v1.0.0", version)
|
assert.Equal(t, "v1.0.0", version)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseSource_Good_VersionWithoutPrefix(t *testing.T) {
|
func TestParseSource_Good_VersionWithoutPrefix_Good(t *testing.T) {
|
||||||
org, repo, version, err := ParseSource("org/repo@1.2.3")
|
org, repo, version, err := ParseSource("org/repo@1.2.3")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "org", org)
|
assert.Equal(t, "org", org)
|
||||||
|
|
@ -131,37 +131,37 @@ func TestParseSource_Good_VersionWithoutPrefix(t *testing.T) {
|
||||||
assert.Equal(t, "1.2.3", version)
|
assert.Equal(t, "1.2.3", version)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseSource_Bad_Empty(t *testing.T) {
|
func TestParseSource_Bad_Empty_Good(t *testing.T) {
|
||||||
_, _, _, err := ParseSource("")
|
_, _, _, err := ParseSource("")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Contains(t, err.Error(), "source is empty")
|
assert.Contains(t, err.Error(), "source is empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseSource_Bad_NoSlash(t *testing.T) {
|
func TestParseSource_Bad_NoSlash_Good(t *testing.T) {
|
||||||
_, _, _, err := ParseSource("just-a-name")
|
_, _, _, err := ParseSource("just-a-name")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Contains(t, err.Error(), "org/repo")
|
assert.Contains(t, err.Error(), "org/repo")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseSource_Bad_TooManySlashes(t *testing.T) {
|
func TestParseSource_Bad_TooManySlashes_Good(t *testing.T) {
|
||||||
_, _, _, err := ParseSource("a/b/c")
|
_, _, _, err := ParseSource("a/b/c")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Contains(t, err.Error(), "org/repo")
|
assert.Contains(t, err.Error(), "org/repo")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseSource_Bad_EmptyOrg(t *testing.T) {
|
func TestParseSource_Bad_EmptyOrg_Good(t *testing.T) {
|
||||||
_, _, _, err := ParseSource("/repo")
|
_, _, _, err := ParseSource("/repo")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Contains(t, err.Error(), "org/repo")
|
assert.Contains(t, err.Error(), "org/repo")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseSource_Bad_EmptyRepo(t *testing.T) {
|
func TestParseSource_Bad_EmptyRepo_Good(t *testing.T) {
|
||||||
_, _, _, err := ParseSource("org/")
|
_, _, _, err := ParseSource("org/")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Contains(t, err.Error(), "org/repo")
|
assert.Contains(t, err.Error(), "org/repo")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseSource_Bad_EmptyVersion(t *testing.T) {
|
func TestParseSource_Bad_EmptyVersion_Good(t *testing.T) {
|
||||||
_, _, _, err := ParseSource("org/repo@")
|
_, _, _, err := ParseSource("org/repo@")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Contains(t, err.Error(), "version is empty")
|
assert.Contains(t, err.Error(), "version is empty")
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ func NewLoader(m io.Medium, baseDir string) *Loader {
|
||||||
|
|
||||||
// Discover finds all plugin directories under baseDir and returns their manifests.
|
// Discover finds all plugin directories under baseDir and returns their manifests.
|
||||||
// Directories without a valid plugin.json are silently skipped.
|
// Directories without a valid plugin.json are silently skipped.
|
||||||
|
// Usage: Discover(...)
|
||||||
func (l *Loader) Discover() ([]*Manifest, error) {
|
func (l *Loader) Discover() ([]*Manifest, error) {
|
||||||
entries, err := l.medium.List(l.baseDir)
|
entries, err := l.medium.List(l.baseDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -50,6 +51,7 @@ func (l *Loader) Discover() ([]*Manifest, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadPlugin loads a single plugin's manifest by name.
|
// LoadPlugin loads a single plugin's manifest by name.
|
||||||
|
// Usage: LoadPlugin(...)
|
||||||
func (l *Loader) LoadPlugin(name string) (*Manifest, error) {
|
func (l *Loader) LoadPlugin(name string) (*Manifest, error) {
|
||||||
manifestPath := filepath.Join(l.baseDir, name, "plugin.json")
|
manifestPath := filepath.Join(l.baseDir, name, "plugin.json")
|
||||||
manifest, err := LoadManifest(l.medium, manifestPath)
|
manifest, err := LoadManifest(l.medium, manifestPath)
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ func TestLoader_Discover_Good(t *testing.T) {
|
||||||
assert.True(t, names["plugin-b"])
|
assert.True(t, names["plugin-b"])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoader_Discover_Good_SkipsInvalidPlugins(t *testing.T) {
|
func TestLoader_Discover_Good_SkipsInvalidPlugins_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
baseDir := "/home/user/.core/plugins"
|
baseDir := "/home/user/.core/plugins"
|
||||||
|
|
||||||
|
|
@ -70,7 +70,7 @@ func TestLoader_Discover_Good_SkipsInvalidPlugins(t *testing.T) {
|
||||||
assert.Equal(t, "good-plugin", manifests[0].Name)
|
assert.Equal(t, "good-plugin", manifests[0].Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoader_Discover_Good_SkipsFiles(t *testing.T) {
|
func TestLoader_Discover_Good_SkipsFiles_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
baseDir := "/home/user/.core/plugins"
|
baseDir := "/home/user/.core/plugins"
|
||||||
|
|
||||||
|
|
@ -91,7 +91,7 @@ func TestLoader_Discover_Good_SkipsFiles(t *testing.T) {
|
||||||
assert.Equal(t, "real-plugin", manifests[0].Name)
|
assert.Equal(t, "real-plugin", manifests[0].Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoader_Discover_Good_EmptyDirectory(t *testing.T) {
|
func TestLoader_Discover_Good_EmptyDirectory_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
baseDir := "/home/user/.core/plugins"
|
baseDir := "/home/user/.core/plugins"
|
||||||
m.Dirs[baseDir] = true
|
m.Dirs[baseDir] = true
|
||||||
|
|
@ -122,7 +122,7 @@ func TestLoader_LoadPlugin_Good(t *testing.T) {
|
||||||
assert.Equal(t, "1.0.0", manifest.Version)
|
assert.Equal(t, "1.0.0", manifest.Version)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoader_LoadPlugin_Bad_NotFound(t *testing.T) {
|
func TestLoader_LoadPlugin_Bad_NotFound_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
loader := NewLoader(m, "/home/user/.core/plugins")
|
loader := NewLoader(m, "/home/user/.core/plugins")
|
||||||
|
|
||||||
|
|
@ -131,7 +131,7 @@ func TestLoader_LoadPlugin_Bad_NotFound(t *testing.T) {
|
||||||
assert.Contains(t, err.Error(), "failed to load plugin")
|
assert.Contains(t, err.Error(), "failed to load plugin")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoader_LoadPlugin_Bad_InvalidManifest(t *testing.T) {
|
func TestLoader_LoadPlugin_Bad_InvalidManifest_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
baseDir := "/home/user/.core/plugins"
|
baseDir := "/home/user/.core/plugins"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ func LoadManifest(m io.Medium, path string) (*Manifest, error) {
|
||||||
|
|
||||||
// Validate checks the manifest for required fields.
|
// Validate checks the manifest for required fields.
|
||||||
// Returns an error if name, version, or entrypoint are missing.
|
// Returns an error if name, version, or entrypoint are missing.
|
||||||
|
// Usage: Validate(...)
|
||||||
func (m *Manifest) Validate() error {
|
func (m *Manifest) Validate() error {
|
||||||
if m.Name == "" {
|
if m.Name == "" {
|
||||||
return coreerr.E("plugin.Manifest.Validate", "name is required", nil)
|
return coreerr.E("plugin.Manifest.Validate", "name is required", nil)
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ func TestLoadManifest_Good(t *testing.T) {
|
||||||
assert.Equal(t, "0.5.0", manifest.MinVersion)
|
assert.Equal(t, "0.5.0", manifest.MinVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadManifest_Good_MinimalFields(t *testing.T) {
|
func TestLoadManifest_Good_MinimalFields_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
m.Files["plugin.json"] = `{
|
m.Files["plugin.json"] = `{
|
||||||
"name": "minimal",
|
"name": "minimal",
|
||||||
|
|
@ -49,7 +49,7 @@ func TestLoadManifest_Good_MinimalFields(t *testing.T) {
|
||||||
assert.Empty(t, manifest.MinVersion)
|
assert.Empty(t, manifest.MinVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadManifest_Bad_FileNotFound(t *testing.T) {
|
func TestLoadManifest_Bad_FileNotFound_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
|
|
||||||
_, err := LoadManifest(m, "nonexistent/plugin.json")
|
_, err := LoadManifest(m, "nonexistent/plugin.json")
|
||||||
|
|
@ -57,7 +57,7 @@ func TestLoadManifest_Bad_FileNotFound(t *testing.T) {
|
||||||
assert.Contains(t, err.Error(), "failed to read manifest")
|
assert.Contains(t, err.Error(), "failed to read manifest")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadManifest_Bad_InvalidJSON(t *testing.T) {
|
func TestLoadManifest_Bad_InvalidJSON_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
m.Files["plugin.json"] = `{invalid json}`
|
m.Files["plugin.json"] = `{invalid json}`
|
||||||
|
|
||||||
|
|
@ -77,7 +77,7 @@ func TestManifest_Validate_Good(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestManifest_Validate_Bad_MissingName(t *testing.T) {
|
func TestManifest_Validate_Bad_MissingName_Good(t *testing.T) {
|
||||||
manifest := &Manifest{
|
manifest := &Manifest{
|
||||||
Version: "1.0.0",
|
Version: "1.0.0",
|
||||||
Entrypoint: "main.go",
|
Entrypoint: "main.go",
|
||||||
|
|
@ -88,7 +88,7 @@ func TestManifest_Validate_Bad_MissingName(t *testing.T) {
|
||||||
assert.Contains(t, err.Error(), "name is required")
|
assert.Contains(t, err.Error(), "name is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestManifest_Validate_Bad_MissingVersion(t *testing.T) {
|
func TestManifest_Validate_Bad_MissingVersion_Good(t *testing.T) {
|
||||||
manifest := &Manifest{
|
manifest := &Manifest{
|
||||||
Name: "test-plugin",
|
Name: "test-plugin",
|
||||||
Entrypoint: "main.go",
|
Entrypoint: "main.go",
|
||||||
|
|
@ -99,7 +99,7 @@ func TestManifest_Validate_Bad_MissingVersion(t *testing.T) {
|
||||||
assert.Contains(t, err.Error(), "version is required")
|
assert.Contains(t, err.Error(), "version is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestManifest_Validate_Bad_MissingEntrypoint(t *testing.T) {
|
func TestManifest_Validate_Bad_MissingEntrypoint_Good(t *testing.T) {
|
||||||
manifest := &Manifest{
|
manifest := &Manifest{
|
||||||
Name: "test-plugin",
|
Name: "test-plugin",
|
||||||
Version: "1.0.0",
|
Version: "1.0.0",
|
||||||
|
|
|
||||||
|
|
@ -41,16 +41,21 @@ type BasePlugin struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name returns the plugin name.
|
// Name returns the plugin name.
|
||||||
|
// Usage: Name(...)
|
||||||
func (p *BasePlugin) Name() string { return p.PluginName }
|
func (p *BasePlugin) Name() string { return p.PluginName }
|
||||||
|
|
||||||
// Version returns the plugin version.
|
// Version returns the plugin version.
|
||||||
|
// Usage: Version(...)
|
||||||
func (p *BasePlugin) Version() string { return p.PluginVersion }
|
func (p *BasePlugin) Version() string { return p.PluginVersion }
|
||||||
|
|
||||||
// Init is a no-op default implementation.
|
// Init is a no-op default implementation.
|
||||||
|
// Usage: Init(...)
|
||||||
func (p *BasePlugin) Init(_ context.Context) error { return nil }
|
func (p *BasePlugin) Init(_ context.Context) error { return nil }
|
||||||
|
|
||||||
// Start is a no-op default implementation.
|
// Start is a no-op default implementation.
|
||||||
|
// Usage: Start(...)
|
||||||
func (p *BasePlugin) Start(_ context.Context) error { return nil }
|
func (p *BasePlugin) Start(_ context.Context) error { return nil }
|
||||||
|
|
||||||
// Stop is a no-op default implementation.
|
// Stop is a no-op default implementation.
|
||||||
|
// Usage: Stop(...)
|
||||||
func (p *BasePlugin) Stop(_ context.Context) error { return nil }
|
func (p *BasePlugin) Stop(_ context.Context) error { return nil }
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ func TestBasePlugin_Good(t *testing.T) {
|
||||||
assert.NoError(t, p.Stop(ctx))
|
assert.NoError(t, p.Stop(ctx))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBasePlugin_Good_EmptyFields(t *testing.T) {
|
func TestBasePlugin_Good_EmptyFields_Good(t *testing.T) {
|
||||||
p := &BasePlugin{}
|
p := &BasePlugin{}
|
||||||
|
|
||||||
assert.Equal(t, "", p.Name())
|
assert.Equal(t, "", p.Name())
|
||||||
|
|
@ -36,6 +36,6 @@ func TestBasePlugin_Good_EmptyFields(t *testing.T) {
|
||||||
assert.NoError(t, p.Stop(ctx))
|
assert.NoError(t, p.Stop(ctx))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBasePlugin_Good_ImplementsPlugin(t *testing.T) {
|
func TestBasePlugin_Good_ImplementsPlugin_Good(t *testing.T) {
|
||||||
var _ Plugin = &BasePlugin{}
|
var _ Plugin = &BasePlugin{}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ func NewRegistry(m io.Medium, basePath string) *Registry {
|
||||||
}
|
}
|
||||||
|
|
||||||
// List returns all installed plugins sorted by name.
|
// List returns all installed plugins sorted by name.
|
||||||
|
// Usage: List(...)
|
||||||
func (r *Registry) List() []*PluginConfig {
|
func (r *Registry) List() []*PluginConfig {
|
||||||
result := make([]*PluginConfig, 0, len(r.plugins))
|
result := make([]*PluginConfig, 0, len(r.plugins))
|
||||||
for _, cfg := range r.plugins {
|
for _, cfg := range r.plugins {
|
||||||
|
|
@ -45,12 +46,14 @@ func (r *Registry) List() []*PluginConfig {
|
||||||
|
|
||||||
// Get returns a plugin by name.
|
// Get returns a plugin by name.
|
||||||
// The second return value indicates whether the plugin was found.
|
// The second return value indicates whether the plugin was found.
|
||||||
|
// Usage: Get(...)
|
||||||
func (r *Registry) Get(name string) (*PluginConfig, bool) {
|
func (r *Registry) Get(name string) (*PluginConfig, bool) {
|
||||||
cfg, ok := r.plugins[name]
|
cfg, ok := r.plugins[name]
|
||||||
return cfg, ok
|
return cfg, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add registers a plugin in the registry.
|
// Add registers a plugin in the registry.
|
||||||
|
// Usage: Add(...)
|
||||||
func (r *Registry) Add(cfg *PluginConfig) error {
|
func (r *Registry) Add(cfg *PluginConfig) error {
|
||||||
if cfg.Name == "" {
|
if cfg.Name == "" {
|
||||||
return coreerr.E("plugin.Registry.Add", "plugin name is required", nil)
|
return coreerr.E("plugin.Registry.Add", "plugin name is required", nil)
|
||||||
|
|
@ -60,6 +63,7 @@ func (r *Registry) Add(cfg *PluginConfig) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove unregisters a plugin from the registry.
|
// Remove unregisters a plugin from the registry.
|
||||||
|
// Usage: Remove(...)
|
||||||
func (r *Registry) Remove(name string) error {
|
func (r *Registry) Remove(name string) error {
|
||||||
if _, ok := r.plugins[name]; !ok {
|
if _, ok := r.plugins[name]; !ok {
|
||||||
return coreerr.E("plugin.Registry.Remove", "plugin not found: "+name, nil)
|
return coreerr.E("plugin.Registry.Remove", "plugin not found: "+name, nil)
|
||||||
|
|
@ -75,6 +79,7 @@ func (r *Registry) registryPath() string {
|
||||||
|
|
||||||
// Load reads the plugin registry from disk.
|
// Load reads the plugin registry from disk.
|
||||||
// If the registry file does not exist, the registry starts empty.
|
// If the registry file does not exist, the registry starts empty.
|
||||||
|
// Usage: Load(...)
|
||||||
func (r *Registry) Load() error {
|
func (r *Registry) Load() error {
|
||||||
path := r.registryPath()
|
path := r.registryPath()
|
||||||
|
|
||||||
|
|
@ -102,6 +107,7 @@ func (r *Registry) Load() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save writes the plugin registry to disk.
|
// Save writes the plugin registry to disk.
|
||||||
|
// Usage: Save(...)
|
||||||
func (r *Registry) Save() error {
|
func (r *Registry) Save() error {
|
||||||
if err := r.medium.EnsureDir(r.basePath); err != nil {
|
if err := r.medium.EnsureDir(r.basePath); err != nil {
|
||||||
return coreerr.E("plugin.Registry.Save", "failed to create plugin directory", err)
|
return coreerr.E("plugin.Registry.Save", "failed to create plugin directory", err)
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ func TestRegistry_Add_Good(t *testing.T) {
|
||||||
assert.Equal(t, "1.0.0", list[0].Version)
|
assert.Equal(t, "1.0.0", list[0].Version)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRegistry_Add_Bad_EmptyName(t *testing.T) {
|
func TestRegistry_Add_Bad_EmptyName_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
reg := NewRegistry(m, "/home/user/.core/plugins")
|
reg := NewRegistry(m, "/home/user/.core/plugins")
|
||||||
|
|
||||||
|
|
@ -68,7 +68,7 @@ func TestRegistry_Get_Good(t *testing.T) {
|
||||||
assert.Equal(t, "2.0.0", cfg.Version)
|
assert.Equal(t, "2.0.0", cfg.Version)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRegistry_Get_Bad_NotFound(t *testing.T) {
|
func TestRegistry_Get_Bad_NotFound_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
reg := NewRegistry(m, "/home/user/.core/plugins")
|
reg := NewRegistry(m, "/home/user/.core/plugins")
|
||||||
|
|
||||||
|
|
@ -77,7 +77,7 @@ func TestRegistry_Get_Bad_NotFound(t *testing.T) {
|
||||||
assert.Nil(t, cfg)
|
assert.Nil(t, cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRegistry_Remove_Bad_NotFound(t *testing.T) {
|
func TestRegistry_Remove_Bad_NotFound_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
reg := NewRegistry(m, "/home/user/.core/plugins")
|
reg := NewRegistry(m, "/home/user/.core/plugins")
|
||||||
|
|
||||||
|
|
@ -128,7 +128,7 @@ func TestRegistry_SaveLoad_Good(t *testing.T) {
|
||||||
assert.False(t, b.Enabled)
|
assert.False(t, b.Enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRegistry_Load_Good_EmptyWhenNoFile(t *testing.T) {
|
func TestRegistry_Load_Good_EmptyWhenNoFile_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
reg := NewRegistry(m, "/home/user/.core/plugins")
|
reg := NewRegistry(m, "/home/user/.core/plugins")
|
||||||
|
|
||||||
|
|
@ -137,7 +137,7 @@ func TestRegistry_Load_Good_EmptyWhenNoFile(t *testing.T) {
|
||||||
assert.Empty(t, reg.List())
|
assert.Empty(t, reg.List())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRegistry_Load_Bad_InvalidJSON(t *testing.T) {
|
func TestRegistry_Load_Bad_InvalidJSON_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
basePath := "/home/user/.core/plugins"
|
basePath := "/home/user/.core/plugins"
|
||||||
_ = m.Write(basePath+"/registry.json", "not valid json {{{")
|
_ = m.Write(basePath+"/registry.json", "not valid json {{{")
|
||||||
|
|
@ -148,7 +148,7 @@ func TestRegistry_Load_Bad_InvalidJSON(t *testing.T) {
|
||||||
assert.Contains(t, err.Error(), "failed to parse registry")
|
assert.Contains(t, err.Error(), "failed to parse registry")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRegistry_Load_Good_NullJSON(t *testing.T) {
|
func TestRegistry_Load_Good_NullJSON_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
basePath := "/home/user/.core/plugins"
|
basePath := "/home/user/.core/plugins"
|
||||||
_ = m.Write(basePath+"/registry.json", "null")
|
_ = m.Write(basePath+"/registry.json", "null")
|
||||||
|
|
@ -159,7 +159,7 @@ func TestRegistry_Load_Good_NullJSON(t *testing.T) {
|
||||||
assert.Empty(t, reg.List())
|
assert.Empty(t, reg.List())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRegistry_Save_Good_CreatesDir(t *testing.T) {
|
func TestRegistry_Save_Good_CreatesDir_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
basePath := "/home/user/.core/plugins"
|
basePath := "/home/user/.core/plugins"
|
||||||
reg := NewRegistry(m, basePath)
|
reg := NewRegistry(m, basePath)
|
||||||
|
|
@ -172,7 +172,7 @@ func TestRegistry_Save_Good_CreatesDir(t *testing.T) {
|
||||||
assert.True(t, m.IsFile(basePath+"/registry.json"))
|
assert.True(t, m.IsFile(basePath+"/registry.json"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRegistry_List_Good_Sorted(t *testing.T) {
|
func TestRegistry_List_Good_Sorted_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
reg := NewRegistry(m, "/plugins")
|
reg := NewRegistry(m, "/plugins")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ func TestGitState_LoadSave_Good(t *testing.T) {
|
||||||
assert.Equal(t, []string{"core-php", "core-tenant"}, loaded.Agents["cladius"].Active)
|
assert.Equal(t, []string{"core-php", "core-tenant"}, loaded.Agents["cladius"].Active)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGitState_Load_Good_NoFile(t *testing.T) {
|
func TestGitState_Load_Good_NoFile_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
_ = m.EnsureDir("/workspace/.core")
|
_ = m.EnsureDir("/workspace/.core")
|
||||||
|
|
||||||
|
|
@ -60,7 +60,7 @@ func TestGitState_Load_Good_NoFile(t *testing.T) {
|
||||||
assert.Empty(t, gs.Agents)
|
assert.Empty(t, gs.Agents)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGitState_Load_Bad_InvalidYAML(t *testing.T) {
|
func TestGitState_Load_Bad_InvalidYAML_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
_ = m.Write("/workspace/.core/git.yaml", "{{{{not yaml")
|
_ = m.Write("/workspace/.core/git.yaml", "{{{{not yaml")
|
||||||
|
|
||||||
|
|
@ -109,7 +109,7 @@ func TestGitState_UpdateRepo_Good(t *testing.T) {
|
||||||
assert.Equal(t, 1, r.Behind)
|
assert.Equal(t, 1, r.Behind)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGitState_UpdateRepo_Good_Overwrite(t *testing.T) {
|
func TestGitState_UpdateRepo_Good_Overwrite_Good(t *testing.T) {
|
||||||
gs := NewGitState()
|
gs := NewGitState()
|
||||||
gs.UpdateRepo("core-php", "main", "origin", 1, 0)
|
gs.UpdateRepo("core-php", "main", "origin", 1, 0)
|
||||||
gs.UpdateRepo("core-php", "main", "origin", 0, 0)
|
gs.UpdateRepo("core-php", "main", "origin", 0, 0)
|
||||||
|
|
@ -131,7 +131,7 @@ func TestGitState_Heartbeat_Good(t *testing.T) {
|
||||||
assert.True(t, agent.LastSeen.After(before) || agent.LastSeen.Equal(before))
|
assert.True(t, agent.LastSeen.After(before) || agent.LastSeen.Equal(before))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGitState_Heartbeat_Good_Updates(t *testing.T) {
|
func TestGitState_Heartbeat_Good_Updates_Good(t *testing.T) {
|
||||||
gs := NewGitState()
|
gs := NewGitState()
|
||||||
gs.Heartbeat("cladius", []string{"core-php"})
|
gs.Heartbeat("cladius", []string{"core-php"})
|
||||||
gs.Heartbeat("cladius", []string{"core-php", "core-tenant"})
|
gs.Heartbeat("cladius", []string{"core-php", "core-tenant"})
|
||||||
|
|
@ -157,7 +157,7 @@ func TestGitState_StaleAgents_Good(t *testing.T) {
|
||||||
assert.NotContains(t, stale, "fresh")
|
assert.NotContains(t, stale, "fresh")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGitState_StaleAgents_Good_NoneStale(t *testing.T) {
|
func TestGitState_StaleAgents_Good_NoneStale_Good(t *testing.T) {
|
||||||
gs := NewGitState()
|
gs := NewGitState()
|
||||||
gs.Heartbeat("cladius", []string{"core-php"})
|
gs.Heartbeat("cladius", []string{"core-php"})
|
||||||
|
|
||||||
|
|
@ -177,7 +177,7 @@ func TestGitState_ActiveAgentsFor_Good(t *testing.T) {
|
||||||
assert.NotContains(t, agents, "athena")
|
assert.NotContains(t, agents, "athena")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGitState_ActiveAgentsFor_Good_IgnoresStale(t *testing.T) {
|
func TestGitState_ActiveAgentsFor_Good_IgnoresStale_Good(t *testing.T) {
|
||||||
gs := NewGitState()
|
gs := NewGitState()
|
||||||
gs.Agents["gone"] = &AgentState{
|
gs.Agents["gone"] = &AgentState{
|
||||||
LastSeen: time.Now().Add(-20 * time.Minute),
|
LastSeen: time.Now().Add(-20 * time.Minute),
|
||||||
|
|
@ -188,7 +188,7 @@ func TestGitState_ActiveAgentsFor_Good_IgnoresStale(t *testing.T) {
|
||||||
assert.Empty(t, agents)
|
assert.Empty(t, agents)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGitState_ActiveAgentsFor_Good_NoMatch(t *testing.T) {
|
func TestGitState_ActiveAgentsFor_Good_NoMatch_Good(t *testing.T) {
|
||||||
gs := NewGitState()
|
gs := NewGitState()
|
||||||
gs.Heartbeat("cladius", []string{"core-php"})
|
gs.Heartbeat("cladius", []string{"core-php"})
|
||||||
|
|
||||||
|
|
@ -198,18 +198,18 @@ func TestGitState_ActiveAgentsFor_Good_NoMatch(t *testing.T) {
|
||||||
|
|
||||||
// ── NeedsPull ──────────────────────────────────────────────────────
|
// ── NeedsPull ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
func TestGitState_NeedsPull_Good_NeverPulled(t *testing.T) {
|
func TestGitState_NeedsPull_Good_NeverPulled_Good(t *testing.T) {
|
||||||
gs := NewGitState()
|
gs := NewGitState()
|
||||||
assert.True(t, gs.NeedsPull("core-php", 5*time.Minute))
|
assert.True(t, gs.NeedsPull("core-php", 5*time.Minute))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGitState_NeedsPull_Good_RecentPull(t *testing.T) {
|
func TestGitState_NeedsPull_Good_RecentPull_Good(t *testing.T) {
|
||||||
gs := NewGitState()
|
gs := NewGitState()
|
||||||
gs.TouchPull("core-php")
|
gs.TouchPull("core-php")
|
||||||
assert.False(t, gs.NeedsPull("core-php", 5*time.Minute))
|
assert.False(t, gs.NeedsPull("core-php", 5*time.Minute))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGitState_NeedsPull_Good_StalePull(t *testing.T) {
|
func TestGitState_NeedsPull_Good_StalePull_Good(t *testing.T) {
|
||||||
gs := NewGitState()
|
gs := NewGitState()
|
||||||
gs.Repos["core-php"] = &RepoGitState{
|
gs.Repos["core-php"] = &RepoGitState{
|
||||||
LastPull: time.Now().Add(-10 * time.Minute),
|
LastPull: time.Now().Add(-10 * time.Minute),
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ func TestKBConfig_LoadSave_Good(t *testing.T) {
|
||||||
assert.True(t, loaded.Wiki.Enabled)
|
assert.True(t, loaded.Wiki.Enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestKBConfig_Load_Good_NoFile(t *testing.T) {
|
func TestKBConfig_Load_Good_NoFile_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
_ = m.EnsureDir("/workspace/.core")
|
_ = m.EnsureDir("/workspace/.core")
|
||||||
|
|
||||||
|
|
@ -56,7 +56,7 @@ func TestKBConfig_Load_Good_NoFile(t *testing.T) {
|
||||||
assert.Equal(t, DefaultKBConfig().Search.Collection, kb.Search.Collection)
|
assert.Equal(t, DefaultKBConfig().Search.Collection, kb.Search.Collection)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestKBConfig_Load_Good_PartialOverride(t *testing.T) {
|
func TestKBConfig_Load_Good_PartialOverride_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
_ = m.Write("/workspace/.core/kb.yaml", `
|
_ = m.Write("/workspace/.core/kb.yaml", `
|
||||||
version: 1
|
version: 1
|
||||||
|
|
@ -75,7 +75,7 @@ search:
|
||||||
assert.Equal(t, "embeddinggemma", kb.Search.EmbedModel)
|
assert.Equal(t, "embeddinggemma", kb.Search.EmbedModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestKBConfig_Load_Bad_InvalidYAML(t *testing.T) {
|
func TestKBConfig_Load_Bad_InvalidYAML_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
_ = m.Write("/workspace/.core/kb.yaml", "{{{{broken")
|
_ = m.Write("/workspace/.core/kb.yaml", "{{{{broken")
|
||||||
|
|
||||||
|
|
@ -92,7 +92,7 @@ func TestKBConfig_WikiRepoURL_Good(t *testing.T) {
|
||||||
assert.Equal(t, "ssh://git@forge.lthn.ai:2223/core/go-scm.wiki.git", url)
|
assert.Equal(t, "ssh://git@forge.lthn.ai:2223/core/go-scm.wiki.git", url)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestKBConfig_WikiRepoURL_Good_CustomRemote(t *testing.T) {
|
func TestKBConfig_WikiRepoURL_Good_CustomRemote_Good(t *testing.T) {
|
||||||
kb := &KBConfig{
|
kb := &KBConfig{
|
||||||
Wiki: WikiConfig{Remote: "ssh://git@git.example.com/org"},
|
Wiki: WikiConfig{Remote: "ssh://git@git.example.com/org"},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ repos:
|
||||||
assert.Equal(t, reg, repo.registry)
|
assert.Equal(t, reg, repo.registry)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadRegistry_Good_WithDefaults(t *testing.T) {
|
func TestLoadRegistry_Good_WithDefaults_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
yaml := `
|
yaml := `
|
||||||
version: 1
|
version: 1
|
||||||
|
|
@ -71,7 +71,7 @@ repos:
|
||||||
assert.Equal(t, "github-actions", admin.CI)
|
assert.Equal(t, "github-actions", admin.CI)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadRegistry_Good_CustomRepoPath(t *testing.T) {
|
func TestLoadRegistry_Good_CustomRepoPath_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
yaml := `
|
yaml := `
|
||||||
version: 1
|
version: 1
|
||||||
|
|
@ -92,7 +92,7 @@ repos:
|
||||||
assert.Equal(t, "/opt/special-repo", repo.Path)
|
assert.Equal(t, "/opt/special-repo", repo.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadRegistry_Good_CIOverride(t *testing.T) {
|
func TestLoadRegistry_Good_CIOverride_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
yaml := `
|
yaml := `
|
||||||
version: 1
|
version: 1
|
||||||
|
|
@ -119,14 +119,14 @@ repos:
|
||||||
assert.Equal(t, "custom-ci", b.CI)
|
assert.Equal(t, "custom-ci", b.CI)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadRegistry_Bad_FileNotFound(t *testing.T) {
|
func TestLoadRegistry_Bad_FileNotFound_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
_, err := LoadRegistry(m, "/nonexistent/repos.yaml")
|
_, err := LoadRegistry(m, "/nonexistent/repos.yaml")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Contains(t, err.Error(), "failed to read")
|
assert.Contains(t, err.Error(), "failed to read")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadRegistry_Bad_InvalidYAML(t *testing.T) {
|
func TestLoadRegistry_Bad_InvalidYAML_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
_ = m.Write("/tmp/bad.yaml", "{{{{not yaml at all")
|
_ = m.Write("/tmp/bad.yaml", "{{{{not yaml at all")
|
||||||
|
|
||||||
|
|
@ -180,7 +180,7 @@ func TestRegistry_Get_Good(t *testing.T) {
|
||||||
assert.Equal(t, "core-php", repo.Name)
|
assert.Equal(t, "core-php", repo.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRegistry_Get_Bad_NotFound(t *testing.T) {
|
func TestRegistry_Get_Bad_NotFound_Good(t *testing.T) {
|
||||||
reg := newTestRegistry(t)
|
reg := newTestRegistry(t)
|
||||||
_, ok := reg.Get("nonexistent")
|
_, ok := reg.Get("nonexistent")
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
|
|
@ -200,7 +200,7 @@ func TestRegistry_ByType_Good(t *testing.T) {
|
||||||
assert.Len(t, products, 1)
|
assert.Len(t, products, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRegistry_ByType_Good_NoMatch(t *testing.T) {
|
func TestRegistry_ByType_Good_NoMatch_Good(t *testing.T) {
|
||||||
reg := newTestRegistry(t)
|
reg := newTestRegistry(t)
|
||||||
templates := reg.ByType("template")
|
templates := reg.ByType("template")
|
||||||
assert.Empty(t, templates)
|
assert.Empty(t, templates)
|
||||||
|
|
@ -242,7 +242,7 @@ func TopologicalOrder(reg *Registry) ([]*Repo, error) {
|
||||||
return reg.TopologicalOrder()
|
return reg.TopologicalOrder()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTopologicalOrder_Bad_CircularDep(t *testing.T) {
|
func TestTopologicalOrder_Bad_CircularDep_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
yaml := `
|
yaml := `
|
||||||
version: 1
|
version: 1
|
||||||
|
|
@ -265,7 +265,7 @@ repos:
|
||||||
assert.Contains(t, err.Error(), "circular dependency")
|
assert.Contains(t, err.Error(), "circular dependency")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTopologicalOrder_Bad_UnknownDep(t *testing.T) {
|
func TestTopologicalOrder_Bad_UnknownDep_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
yaml := `
|
yaml := `
|
||||||
version: 1
|
version: 1
|
||||||
|
|
@ -285,7 +285,7 @@ repos:
|
||||||
assert.Contains(t, err.Error(), "unknown repo")
|
assert.Contains(t, err.Error(), "unknown repo")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTopologicalOrder_Good_NoDeps(t *testing.T) {
|
func TestTopologicalOrder_Good_NoDeps_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
yaml := `
|
yaml := `
|
||||||
version: 1
|
version: 1
|
||||||
|
|
@ -333,7 +333,7 @@ func TestScanDirectory_Good(t *testing.T) {
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestScanDirectory_Good_DetectsGitHubOrg(t *testing.T) {
|
func TestScanDirectory_Good_DetectsGitHubOrg_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
|
|
||||||
_ = m.EnsureDir("/workspace/my-repo/.git")
|
_ = m.EnsureDir("/workspace/my-repo/.git")
|
||||||
|
|
@ -349,7 +349,7 @@ func TestScanDirectory_Good_DetectsGitHubOrg(t *testing.T) {
|
||||||
assert.Equal(t, "host-uk", reg.Org)
|
assert.Equal(t, "host-uk", reg.Org)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestScanDirectory_Good_DetectsHTTPSOrg(t *testing.T) {
|
func TestScanDirectory_Good_DetectsHTTPSOrg_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
|
|
||||||
_ = m.EnsureDir("/workspace/my-repo/.git")
|
_ = m.EnsureDir("/workspace/my-repo/.git")
|
||||||
|
|
@ -362,7 +362,7 @@ func TestScanDirectory_Good_DetectsHTTPSOrg(t *testing.T) {
|
||||||
assert.Equal(t, "lethean-io", reg.Org)
|
assert.Equal(t, "lethean-io", reg.Org)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestScanDirectory_Good_EmptyDir(t *testing.T) {
|
func TestScanDirectory_Good_EmptyDir_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
_ = m.EnsureDir("/empty")
|
_ = m.EnsureDir("/empty")
|
||||||
|
|
||||||
|
|
@ -372,7 +372,7 @@ func TestScanDirectory_Good_EmptyDir(t *testing.T) {
|
||||||
assert.Equal(t, "", reg.Org)
|
assert.Equal(t, "", reg.Org)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestScanDirectory_Bad_InvalidDir(t *testing.T) {
|
func TestScanDirectory_Bad_InvalidDir_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
_, err := ScanDirectory(m, "/nonexistent")
|
_, err := ScanDirectory(m, "/nonexistent")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
|
|
@ -381,7 +381,7 @@ func TestScanDirectory_Bad_InvalidDir(t *testing.T) {
|
||||||
|
|
||||||
// ── detectOrg ──────────────────────────────────────────────────────
|
// ── detectOrg ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
func TestDetectOrg_Good_SSHRemote(t *testing.T) {
|
func TestDetectOrg_Good_SSHRemote_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
_ = m.Write("/repo/.git/config", `[remote "origin"]
|
_ = m.Write("/repo/.git/config", `[remote "origin"]
|
||||||
url = git@github.com:host-uk/core.git
|
url = git@github.com:host-uk/core.git
|
||||||
|
|
@ -389,7 +389,7 @@ func TestDetectOrg_Good_SSHRemote(t *testing.T) {
|
||||||
assert.Equal(t, "host-uk", detectOrg(m, "/repo"))
|
assert.Equal(t, "host-uk", detectOrg(m, "/repo"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDetectOrg_Good_HTTPSRemote(t *testing.T) {
|
func TestDetectOrg_Good_HTTPSRemote_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
_ = m.Write("/repo/.git/config", `[remote "origin"]
|
_ = m.Write("/repo/.git/config", `[remote "origin"]
|
||||||
url = https://github.com/snider/project.git
|
url = https://github.com/snider/project.git
|
||||||
|
|
@ -397,12 +397,12 @@ func TestDetectOrg_Good_HTTPSRemote(t *testing.T) {
|
||||||
assert.Equal(t, "snider", detectOrg(m, "/repo"))
|
assert.Equal(t, "snider", detectOrg(m, "/repo"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDetectOrg_Bad_NoConfig(t *testing.T) {
|
func TestDetectOrg_Bad_NoConfig_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
assert.Equal(t, "", detectOrg(m, "/nonexistent"))
|
assert.Equal(t, "", detectOrg(m, "/nonexistent"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDetectOrg_Bad_NoRemote(t *testing.T) {
|
func TestDetectOrg_Bad_NoRemote_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
_ = m.Write("/repo/.git/config", `[core]
|
_ = m.Write("/repo/.git/config", `[core]
|
||||||
repositoryformatversion = 0
|
repositoryformatversion = 0
|
||||||
|
|
@ -410,7 +410,7 @@ func TestDetectOrg_Bad_NoRemote(t *testing.T) {
|
||||||
assert.Equal(t, "", detectOrg(m, "/repo"))
|
assert.Equal(t, "", detectOrg(m, "/repo"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDetectOrg_Bad_NonGitHubRemote(t *testing.T) {
|
func TestDetectOrg_Bad_NonGitHubRemote_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
_ = m.Write("/repo/.git/config", `[remote "origin"]
|
_ = m.Write("/repo/.git/config", `[remote "origin"]
|
||||||
url = ssh://git@forge.lthn.ai:2223/core/go.git
|
url = ssh://git@forge.lthn.ai:2223/core/go.git
|
||||||
|
|
@ -420,13 +420,13 @@ func TestDetectOrg_Bad_NonGitHubRemote(t *testing.T) {
|
||||||
|
|
||||||
// ── expandPath ─────────────────────────────────────────────────────
|
// ── expandPath ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
func TestExpandPath_Good_Tilde(t *testing.T) {
|
func TestExpandPath_Good_Tilde_Good(t *testing.T) {
|
||||||
got := expandPath("~/Code/repos")
|
got := expandPath("~/Code/repos")
|
||||||
assert.NotContains(t, got, "~")
|
assert.NotContains(t, got, "~")
|
||||||
assert.Contains(t, got, "Code/repos")
|
assert.Contains(t, got, "Code/repos")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExpandPath_Good_NoTilde(t *testing.T) {
|
func TestExpandPath_Good_NoTilde_Good(t *testing.T) {
|
||||||
assert.Equal(t, "/absolute/path", expandPath("/absolute/path"))
|
assert.Equal(t, "/absolute/path", expandPath("/absolute/path"))
|
||||||
assert.Equal(t, "relative/path", expandPath("relative/path"))
|
assert.Equal(t, "relative/path", expandPath("relative/path"))
|
||||||
}
|
}
|
||||||
|
|
@ -473,14 +473,14 @@ func TestRepo_IsGitRepo_Good(t *testing.T) {
|
||||||
|
|
||||||
// ── getMedium fallback ─────────────────────────────────────────────
|
// ── getMedium fallback ─────────────────────────────────────────────
|
||||||
|
|
||||||
func TestGetMedium_Good_FallbackToLocal(t *testing.T) {
|
func TestGetMedium_Good_FallbackToLocal_Good(t *testing.T) {
|
||||||
repo := &Repo{Name: "orphan", Path: "/tmp/orphan"}
|
repo := &Repo{Name: "orphan", Path: "/tmp/orphan"}
|
||||||
// No registry set — should fall back to io.Local.
|
// No registry set — should fall back to io.Local.
|
||||||
m := repo.getMedium()
|
m := repo.getMedium()
|
||||||
assert.Equal(t, io.Local, m)
|
assert.Equal(t, io.Local, m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetMedium_Good_NilMediumFallback(t *testing.T) {
|
func TestGetMedium_Good_NilMediumFallback_Good(t *testing.T) {
|
||||||
reg := &Registry{} // medium is nil.
|
reg := &Registry{} // medium is nil.
|
||||||
repo := &Repo{Name: "test", registry: reg}
|
repo := &Repo{Name: "test", registry: reg}
|
||||||
m := repo.getMedium()
|
m := repo.getMedium()
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ func TestWorkConfig_LoadSave_Good(t *testing.T) {
|
||||||
assert.True(t, loaded.Sync.AutoPull)
|
assert.True(t, loaded.Sync.AutoPull)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWorkConfig_Load_Good_NoFile(t *testing.T) {
|
func TestWorkConfig_Load_Good_NoFile_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
_ = m.EnsureDir("/workspace/.core")
|
_ = m.EnsureDir("/workspace/.core")
|
||||||
|
|
||||||
|
|
@ -59,7 +59,7 @@ func TestWorkConfig_Load_Good_NoFile(t *testing.T) {
|
||||||
assert.Equal(t, DefaultWorkConfig().Sync.Interval, wc.Sync.Interval)
|
assert.Equal(t, DefaultWorkConfig().Sync.Interval, wc.Sync.Interval)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWorkConfig_Load_Good_PartialOverride(t *testing.T) {
|
func TestWorkConfig_Load_Good_PartialOverride_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
_ = m.Write("/workspace/.core/work.yaml", `
|
_ = m.Write("/workspace/.core/work.yaml", `
|
||||||
version: 1
|
version: 1
|
||||||
|
|
@ -78,7 +78,7 @@ sync:
|
||||||
assert.True(t, wc.Sync.CloneMissing)
|
assert.True(t, wc.Sync.CloneMissing)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWorkConfig_Load_Bad_InvalidYAML(t *testing.T) {
|
func TestWorkConfig_Load_Bad_InvalidYAML_Good(t *testing.T) {
|
||||||
m := io.NewMockMedium()
|
m := io.NewMockMedium()
|
||||||
_ = m.Write("/workspace/.core/work.yaml", "{{{{broken")
|
_ = m.Write("/workspace/.core/work.yaml", "{{{{broken")
|
||||||
|
|
||||||
|
|
@ -96,12 +96,12 @@ func TestWorkConfig_HasTrigger_Good(t *testing.T) {
|
||||||
assert.True(t, wc.HasTrigger("scheduled"))
|
assert.True(t, wc.HasTrigger("scheduled"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWorkConfig_HasTrigger_Bad_NotFound(t *testing.T) {
|
func TestWorkConfig_HasTrigger_Bad_NotFound_Good(t *testing.T) {
|
||||||
wc := DefaultWorkConfig()
|
wc := DefaultWorkConfig()
|
||||||
assert.False(t, wc.HasTrigger("on_deploy"))
|
assert.False(t, wc.HasTrigger("on_deploy"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWorkConfig_HasTrigger_Good_CustomTriggers(t *testing.T) {
|
func TestWorkConfig_HasTrigger_Good_CustomTriggers_Good(t *testing.T) {
|
||||||
wc := &WorkConfig{
|
wc := &WorkConfig{
|
||||||
Version: 1,
|
Version: 1,
|
||||||
Triggers: []string{"on_pr", "manual"},
|
Triggers: []string{"on_pr", "manual"},
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue