Skip to content

Commit

Permalink
Merge pull request #3 from AtomicMegaNerd/2-starfeed-remote-stale-fee…
Browse files Browse the repository at this point in the history
…ds-unsubscribes-from-all-non-github-rss-feeds

Fix unsubscribe bug
  • Loading branch information
AtomicMegaNerd authored Dec 25, 2024
2 parents 55e8936 + 7a25eff commit ee9884b
Show file tree
Hide file tree
Showing 9 changed files with 297 additions and 41 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ jobs:
- name: Run lints
run: task lint

- name: Run tests
run: task test

publish_docker_image:
runs-on: ubuntu-latest
name: Build and Publish Docker image
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.1.2] - 2024-12-25

### Fixed

- Fixed a bug that causes Starfeed to unsubscribe from all non-Github release feeds.
- Added some initial test coverage

## [0.1.1] - 2024-12-24

### Fixed
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,4 +112,6 @@ task test
- [x] Write end-user documentation
- [x] Add some performance profiling
- [x] Draw a cute logo
- [ ] Add some test coverage
- [ ] Add unit tests
- [ ] Add integration tests
- [ ] Add test coverage to Taskfile and to Github Actions
8 changes: 1 addition & 7 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,11 @@

version: '3'

# Please make sure DB_ADDR is set in your .envrc file.
dotenv: [./.env]

env:
out: bin/starfeed
cov_file: cover.out
cov_xml_file: cover.xml
test_xml_file: "test-report.xml"
src: ./cmd/
# GRC is a command line colorizer. If you don't have it installed, you can remove it from the
# command.
grc: $(if command -v grc > /dev/null; then echo "grc"; else echo ""; fi)

tasks:
Expand All @@ -30,7 +24,7 @@ tasks:
deps: [check-deps]
cmds:
- cmd: go build -o {{.out}} {{.src}}
- cmd: codesign -f -s "RCD Local" {{.out}} --deep
- cmd: codesign -f -s "RCD Local Development" {{.out}} --deep
platforms: [darwin] # Only sign the binary on macOS.

run:
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v0.1.1
v0.1.2
28 changes: 14 additions & 14 deletions github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,21 @@ import (
// This object handles buidling an Atom Feed of all starred repos for the authenticated
// user.
type GitHubStarredFeedBuilder struct {
token string // WARNING: Do not log this value as it is a secret
ctx context.Context
client *http.Client
re *regexp.Regexp
token string // WARNING: Do not log this value as it is a secret
ctx context.Context
client *http.Client
nextPageLinkRegex *regexp.Regexp
isRelRepoRegex *regexp.Regexp
}

func NewGitHubStarredFeedBuilder(
token string,
ctx context.Context,
client *http.Client,
) *GitHubStarredFeedBuilder {

return &GitHubStarredFeedBuilder{token: token, ctx: ctx, client: client}
nextPageLinkRegex, _ := regexp.Compile(`<([^>]+)>; rel="next"`)
isRelRepoRegex, _ := regexp.Compile(`^https://github.com/[\w\.\-]+/[\w\.\-]+/releases\.atom`)
return &GitHubStarredFeedBuilder{token, ctx, client, nextPageLinkRegex, isRelRepoRegex}
}

// This will return all starred repos including the Atom feeds for their releases
Expand All @@ -36,13 +38,6 @@ func (gh *GitHubStarredFeedBuilder) GetStarredRepos() (map[string]GitHubRepo, er
getUrl := "http://api.github.com/user/starred?per_page=100"
slog.Debug("Querying Github for starred repos", "url", getUrl)

pattern := `<([^>]+)>; rel="next"`
var err error
gh.re, err = regexp.Compile(pattern)
if err != nil {
return nil, err
}

for {
ghResponse, err := gh.doApiRequest(getUrl)
if err != nil {
Expand Down Expand Up @@ -122,11 +117,16 @@ func (gh *GitHubStarredFeedBuilder) processGithubResponse(r *http.Response) (*Gi
linkRaw := r.Header.Get("link")
links := strings.Split(linkRaw, ",")
for _, link := range links {
matches := gh.re.FindStringSubmatch(link)
matches := gh.nextPageLinkRegex.FindStringSubmatch(link)
if len(matches) == 2 {
return &GithubResponse{data: data, nextPage: matches[1]}, nil
}
}

return &GithubResponse{data: data}, nil
}

// This function returns true if a repoUrl is a Github release repo
func (gh *GitHubStarredFeedBuilder) IsGithubReleasesFeed(feedUrl string) bool {
return gh.isRelRepoRegex.MatchString(feedUrl)
}
215 changes: 215 additions & 0 deletions github/github_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
package github

import (
"context"
"io"
"net/http"
"strings"
"testing"

mocks "github.com/atomicmeganerd/starfeed/utils"
)

const (
// Repo 1
repoName1 = "repo1"
repoHtmlUrl1 = "https://github.com/user/repo1"
repoReleasesUrl1 = "https://github.com/user/repo1/releases.atom"

// Repo 2
repoName2 = "repo2"
repoHtmlUrl2 = "https://github.com/user/repo2"
repoReleasesUrl2 = "https://github.com/user/repo2/releases.atom"

// Repo 3
repoName3 = "repo3"
repoHtmlUrl3 = "https://github.com/user/repo3"
repoReleasesUrl3 = "https://github.com/user/repo3/releases.atom"

// Repo 4
repoName4 = "repo4"
repoHtmlUrl4 = "https://github.com/user/repo4"
repoReleasesUrl4 = "https://github.com/user/repo4/releases.atom"
)

type GetStarredReposTestCase struct {
name string
responses []http.Response
expectedRepos []GitHubRepo
expectError bool
}

func (tc *GetStarredReposTestCase) GetTestObject() *GitHubStarredFeedBuilder {
mockTransport := mocks.NewMockRoundTripper(tc.responses)
mockClient := &http.Client{Transport: &mockTransport}
return NewGitHubStarredFeedBuilder("mockToken", context.Background(), mockClient)
}

func TestGetStarredRepos(t *testing.T) {

testCases := []GetStarredReposTestCase{
{
name: "Single repo with no pages",
responses: []http.Response{
{
Body: io.NopCloser(strings.NewReader(`[
{
"name": "` + repoName1 + `",
"html_url": "` + repoHtmlUrl1 + `",
"releases_url": "` + repoReleasesUrl1 + `"
}
]`),
),
Status: "200 OK",
StatusCode: http.StatusOK,
},
},
expectedRepos: []GitHubRepo{
{
Name: repoName1,
HtmlUrl: repoHtmlUrl1,
FeedUrl: repoReleasesUrl1,
},
},
expectError: false,
},
{
name: "A few repos over multiple pages",
responses: []http.Response{
{
Body: io.NopCloser(strings.NewReader(`[
{
"name": "` + repoName1 + `",
"html_url": "` + repoHtmlUrl1 + `",
"releases_url": "` + repoReleasesUrl1 + `"
},
{
"name": "` + repoName2 + `",
"html_url": "` + repoHtmlUrl2 + `",
"releases_url": "` + repoReleasesUrl2 + `"
}
]`),
),
Status: "200 OK",
StatusCode: http.StatusOK,
Header: http.Header{
"Link": []string{
`<https://api.github.com/user/starred?per_page=2&page=2>; rel="next"`,
},
},
},
{
Body: io.NopCloser(strings.NewReader(`[
{
"name": "` + repoName3 + `",
"html_url": "` + repoHtmlUrl3 + `",
"releases_url": "` + repoReleasesUrl3 + `"
},
{
"name": "` + repoName4 + `",
"html_url": "` + repoHtmlUrl4 + `",
"releases_url": "` + repoReleasesUrl4 + `"
}
]`),
),
Status: "200 OK",
StatusCode: http.StatusOK,
},
},
expectedRepos: []GitHubRepo{
{
Name: repoName1,
HtmlUrl: repoHtmlUrl1,
FeedUrl: repoReleasesUrl1,
},
{
Name: repoName2,
HtmlUrl: repoHtmlUrl2,
FeedUrl: repoReleasesUrl2,
},
{
Name: repoName3,
HtmlUrl: repoHtmlUrl3,

FeedUrl: repoReleasesUrl3,
},
{
Name: repoName4,
HtmlUrl: repoHtmlUrl4,

FeedUrl: repoReleasesUrl4,
},
},
expectError: false,
},
}

for _, tc := range testCases {
gh := tc.GetTestObject()
repos, err := gh.GetStarredRepos()
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}

if len(repos) != len(tc.expectedRepos) {
t.Fatalf("Expected %d repos, got %d", len(tc.expectedRepos), len(repos))
}
}
}

func TestIsGithubReleaseRepo(t *testing.T) {

type TestCase struct {
name string
feedUrl string
expectMatch bool
}

mockClient := http.Client{}
gh := NewGitHubStarredFeedBuilder("", context.Background(), &mockClient)
testCases := []TestCase{
{
name: "Letters only",
feedUrl: "https://github.com/atomicmeganerd/starfeed/releases.atom",
expectMatch: true,
},
{
name: "Handle .",
feedUrl: "https://github.com/EdenEast/nightfox.nvim/releases.atom",
expectMatch: true,
},
{
name: "Handle -",
feedUrl: "https://github.com/nix-community/NixOS-WSL/releases.atom",
expectMatch: true,
},
{
name: "Handle numbers",
feedUrl: "https://github.com/PyO3/pyo3/releases.atom",
expectMatch: true,
},
{
name: "Not Github",
feedUrl: "https://rofl.com/user/repo/releases.atom",
expectMatch: false,
},
{
name: "Not release",
feedUrl: "https://github.com/atomicmeganerd/starfeed/other.atom",
expectMatch: false,
},
}

for _, tc := range testCases {
if tc.expectMatch {
if !gh.IsGithubReleasesFeed(tc.feedUrl) {
t.Errorf("Expected feed %s to match but it did not", tc.feedUrl)
}
} else {
if gh.IsGithubReleasesFeed(tc.feedUrl) {
t.Errorf("Expected feed %s to not match but it did", tc.feedUrl)
}
}
}

}
Loading

0 comments on commit ee9884b

Please sign in to comment.