-
Notifications
You must be signed in to change notification settings - Fork 215
feat: implement recursive CRD discovery and robust GitHub URL parsing #887
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 13 commits
8196b38
067a69f
494fe1e
f177806
9002212
d3cd80e
fb4ff3d
b8ab325
6c4c078
d843222
5715b0f
477ce39
01e3717
0225ce3
fec7814
c01a7c0
9c2f3c5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| package github | ||
|
|
||
| import ( | ||
| "encoding/json" | ||
| "fmt" | ||
| "net/http" | ||
| ) | ||
|
|
||
| func GetDefaultBranchFromGitHub(baseUrl, owner, repo string, client *http.Client) (string, error) { | ||
| if client == nil { | ||
| client = http.DefaultClient | ||
| } | ||
| url := fmt.Sprintf("%s/repos/%s/%s", baseUrl, owner, repo) | ||
| resp, err := client.Get(url) | ||
| if err != nil { | ||
| return "", ErrGetDefaultBranch(err, owner, repo) | ||
| } | ||
| defer resp.Body.Close() | ||
| if resp.StatusCode != 200 { | ||
| return "", ErrGetDefaultBranch(fmt.Errorf("github api: %d", resp.StatusCode), owner, repo) | ||
| } | ||
| var out struct { | ||
| DefaultBranch string `json:"default_branch"` | ||
| } | ||
| if err := json.NewDecoder(resp.Body).Decode(&out); err != nil { | ||
| return "", ErrGetDefaultBranch(err, owner, repo) | ||
| } | ||
| return out.DefaultBranch, nil | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| package github | ||
|
|
||
| import ( | ||
| "encoding/json" | ||
| "fmt" | ||
| "net/http" | ||
| "net/http/httptest" | ||
| "testing" | ||
| ) | ||
|
|
||
| func TestGetGithubRepoBranch(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| owner string | ||
| repo string | ||
| mockStatus int | ||
| mockBody any // Using any allows us to pass structs or raw strings | ||
| wantBranch string | ||
| wantErr bool | ||
| }{ | ||
| { | ||
| name: "valid repo", | ||
| owner: "octocat", | ||
| repo: "Hello-World", | ||
| mockStatus: http.StatusOK, | ||
| mockBody: map[string]string{"default_branch": "main"}, | ||
| wantBranch: "main", | ||
| wantErr: false, | ||
| }, | ||
| { | ||
| name: "valid repo with master branch", | ||
| owner: "octocat", | ||
| repo: "Hello-Mesh", | ||
| mockStatus: http.StatusOK, | ||
| mockBody: map[string]string{"default_branch": "master"}, | ||
| wantBranch: "master", | ||
| wantErr: false, | ||
| }, | ||
| { | ||
| name: "repo not found", | ||
| owner: "octocat", | ||
| repo: "NonExistentRepo", | ||
| mockStatus: http.StatusNotFound, | ||
| mockBody: map[string]string{"message": "Not Found"}, | ||
| wantBranch: "", | ||
| wantErr: true, | ||
| }, | ||
| } | ||
|
|
||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
| expectedPath := fmt.Sprintf("/repos/%s/%s", tt.owner, tt.repo) | ||
| if r.URL.Path != expectedPath { | ||
| t.Errorf("expected path %s, got %s", expectedPath, r.URL.Path) | ||
| } | ||
|
|
||
| w.WriteHeader(tt.mockStatus) | ||
| json.NewEncoder(w).Encode(tt.mockBody) | ||
| })) | ||
| defer server.Close() | ||
|
|
||
| branch, err := GetDefaultBranchFromGitHub(server.URL, tt.owner, tt.repo, server.Client()) | ||
|
|
||
| if (err != nil) != tt.wantErr { | ||
| t.Fatalf("GetDefaultBranchFromGitHub() unexpected error state: %v", err) | ||
| } | ||
| if branch != tt.wantBranch { | ||
| t.Errorf("got branch %q, want %q", branch, tt.wantBranch) | ||
| } | ||
| }) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,145 @@ | ||
| package github | ||
|
|
||
| import ( | ||
| "net/url" | ||
| "testing" | ||
| ) | ||
|
|
||
| func TestExtractRepoDetailsFromSourceURL(t *testing.T) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test makes real network calls to the GitHub API when a URL with only an owner and repo is provided (e.g., Unit tests should be self-contained and not rely on external services. The call to One way to achieve this is to introduce a function variable in In // Add this variable at the package level
var getDefaultBranch = GetDefaultBranchFromGitHub
// In extractRepoDetailsFromSourceURL, change the call to use this variable
// ...
b, err := getDefaultBranch("https://api.github.com", owner, repo, http.DefaultClient)
// ...Then, in func TestExtractRepoDetailsFromSourceURL(t *testing.T) {
// Mock the function
oldGetDefaultBranch := getDefaultBranch
getDefaultBranch = func(baseUrl, owner, repo string, client *http.Client) (string, error) {
// For this test, we can assume the default branch is always "master"
// to match the test expectations.
if owner == "meshery" && repo == "meshkit" {
return "master", nil
}
return "", fmt.Errorf("unexpected repo: %s/%s", owner, repo)
}
// Restore the original function after the test
defer func() { getDefaultBranch = oldGetDefaultBranch }()
// ... rest of the test code
}This change will make your tests fast, reliable, and independent of external services. |
||
| tests := []struct { | ||
| name string | ||
| input string | ||
| wantOwner string | ||
| wantRepo string | ||
| wantBranch string | ||
| wantRoot string | ||
| wantErr bool | ||
| }{ | ||
| { | ||
| name: "owner and repo only", | ||
| input: "git://github.com/meshery/meshkit", | ||
| wantOwner: "meshery", | ||
| wantRepo: "meshkit", | ||
| wantBranch: "master", | ||
| wantRoot: "/**", | ||
| wantErr: false, | ||
| }, | ||
| { | ||
| name: "owner repo and branch", | ||
| input: "git://github.com/meshery/meshkit/master", | ||
| wantOwner: "meshery", | ||
| wantRepo: "meshkit", | ||
| wantBranch: "master", | ||
| wantRoot: "/**", | ||
| wantErr: false, | ||
| }, | ||
| { | ||
| name: "owner repo branch and path", | ||
| input: "git://github.com/meshery/meshkit/master/install/kubernetes", | ||
| wantOwner: "meshery", | ||
| wantRepo: "meshkit", | ||
| wantBranch: "master", | ||
| wantRoot: "install/kubernetes", | ||
| wantErr: false, | ||
| }, | ||
| { | ||
| name: "invalid single component", | ||
| input: "git://github.com/meshery", | ||
| wantErr: true, | ||
| }, | ||
| { | ||
| name: "trailing slash on repo", | ||
| input: "git://github.com/meshery/meshkit/", | ||
| wantOwner: "meshery", | ||
| wantRepo: "meshkit", | ||
| wantBranch: "master", | ||
| wantRoot: "/**", | ||
| wantErr: false, | ||
| }, | ||
| // AFter gemini review these should gail | ||
| { | ||
| name: "trailing slash triggers default branch", | ||
| input: "git://github.com/meshery/meshkit/", // Trailing slash | ||
| wantOwner: "meshery", | ||
| wantRepo: "meshkit", | ||
| wantBranch: "master", // The old code would return "" here | ||
| wantRoot: "/**", | ||
| wantErr: false, | ||
| }, | ||
| { | ||
| name: "nested path depth greater than 4", | ||
| input: "git://github.com/meshery/meshkit/master/install/kubernetes/helm/meshery", | ||
| wantOwner: "meshery", | ||
| wantRepo: "meshkit", | ||
| wantBranch: "master", | ||
| wantRoot: "install/kubernetes/helm/meshery", // The old code would cut this off | ||
| wantErr: false, | ||
| }, | ||
| { | ||
| name: "multiple slashes in path", | ||
| input: "git://github.com/meshery/meshkit///master/", | ||
| wantOwner: "meshery", | ||
| wantRepo: "meshkit", | ||
| wantBranch: "master", | ||
| wantRoot: "/**", | ||
| wantErr: false, | ||
| }, | ||
| { | ||
| name: "GitHub Browser URL - Tree", | ||
| input: "https://github.com/meshery/meshkit/tree/master/install", | ||
| wantOwner: "meshery", | ||
| wantRepo: "meshkit", | ||
| wantBranch: "master", | ||
| wantRoot: "install", | ||
| wantErr: false, | ||
| }, | ||
| { | ||
| name: "GitHub Browser URL - Blob", | ||
| input: "https://github.com/meshery/meshkit/blob/master/models/meshmodel.yaml", | ||
| wantOwner: "meshery", | ||
| wantRepo: "meshkit", | ||
| wantBranch: "master", | ||
| wantRoot: "models/meshmodel.yaml", | ||
| wantErr: false, | ||
| }, | ||
| { | ||
| name: "GitHub Release URL", | ||
| input: "https://github.com/meshery/meshkit/releases/tag/v0.6.0", | ||
| wantOwner: "meshery", | ||
| wantRepo: "meshkit", | ||
| wantBranch: "v0.6.0", | ||
| wantRoot: "/**", | ||
| wantErr: false, | ||
| }, | ||
| } | ||
|
|
||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| u, err := url.Parse(tt.input) | ||
| if err != nil { | ||
| t.Fatalf("failed to parse url %s: %v", tt.input, err) | ||
| } | ||
| gr := GitRepo{URL: u} | ||
| owner, repo, branch, root, err := gr.ExtractRepoDetailsFromSourceURL() | ||
| if (err != nil) != tt.wantErr { | ||
| t.Fatalf("unexpected error state: got err=%v, wantErr=%v", err, tt.wantErr) | ||
| } | ||
| if err != nil { | ||
| // on error we are done | ||
| return | ||
| } | ||
| if owner != tt.wantOwner { | ||
| t.Fatalf("owner: got %q want %q", owner, tt.wantOwner) | ||
| } | ||
| if repo != tt.wantRepo { | ||
| t.Fatalf("repo: got %q want %q", repo, tt.wantRepo) | ||
| } | ||
| if branch != tt.wantBranch { | ||
| t.Fatalf("branch: got %q want %q", branch, tt.wantBranch) | ||
| } | ||
| if root != tt.wantRoot { | ||
| t.Fatalf("root: got %q want %q", root, tt.wantRoot) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.