@@ -61,7 +61,7 @@ func setupProfileEnvWithDeps(t *testing.T, objectStore storage.ObjectStore, repo |
| 61 | tmplFS := fstest.MapFS{ | 61 | tmplFS := fstest.MapFS{ |
| 62 | "_layout.html": {Data: []byte(`{{ define "layout" }}<html><head><title>{{ .Title }}</title></head><body>{{ template "page" . }}</body></html>{{ end }}`)}, | 62 | "_layout.html": {Data: []byte(`{{ define "layout" }}<html><head><title>{{ .Title }}</title></head><body>{{ template "page" . }}</body></html>{{ end }}`)}, |
| 63 | "hello.html": {Data: []byte(`{{ define "page" }}home{{ end }}`)}, | 63 | "hello.html": {Data: []byte(`{{ define "page" }}home{{ end }}`)}, |
| 64 | - "profile/view.html": {Data: []byte(`{{ define "page" }}USER={{.User.Username}} DISPLAY={{.User.DisplayName}}{{ if .IsSelf }} SELF=1{{ end }}{{ if .IsFollowing }} FOLLOWING=1{{ end }} FOLLOWERS={{.FollowersCount}} FOLLOWINGCOUNT={{.FollowingCount}} BIO={{.User.Bio}} VISIBLE={{.VisibleRepoCount}} ORGS={{len .Orgs}} README={{.HasProfileReadme}} CONTRIB={{.Contributions.Total}} PERIOD={{.Contributions.Period}} PRIVATE={{.Contributions.IncludePrivateContributions}} WEEKS={{len .Contributions.Weeks}} YEARS={{len .Contributions.Years}} YEARLINKS={{range .Contributions.Years}}{{.Year}}:{{.Active}}:{{.Href}};{{end}} PINS={{len .PinnedRepos}} PINNAMES={{range .PinnedRepos}}{{.Name}};{{end}} CANDIDATES={{len .PinCandidates}} SELECTED={{range .PinCandidates}}{{if .IsPinned}}{{.Name}};{{end}}{{end}}{{ if .CanCustomizePins }} CUSTOMIZE=1 ACTION={{.ContributionSettingsAction}} RETURN={{.ContributionSettingsReturn}}{{ end }}{{ end }}`)}, | 64 | + "profile/view.html": {Data: []byte(`{{ define "page" }}USER={{.User.Username}} DISPLAY={{.User.DisplayName}}{{ if .IsSelf }} SELF=1{{ end }}{{ if .IsFollowing }} FOLLOWING=1{{ end }} FOLLOWERS={{.FollowersCount}} FOLLOWINGCOUNT={{.FollowingCount}} BIO={{.User.Bio}} VISIBLE={{.VisibleRepoCount}} ORGS={{len .Orgs}} README={{.HasProfileReadme}} CONTRIB={{.Contributions.Total}} PERIOD={{.Contributions.Period}} PRIVATE={{.Contributions.IncludePrivateContributions}} WEEKS={{len .Contributions.Weeks}} YEARS={{len .Contributions.Years}} YEARLINKS={{range .Contributions.Years}}{{.Year}}:{{.Active}}:{{.Href}};{{end}} PINS={{len .PinnedRepos}} PINNAMES={{range .PinnedRepos}}{{.Name}};{{end}} CANDIDATES={{len .PinCandidates}} CANDIDATENAMES={{range .PinCandidates}}{{.OwnerSlug}}/{{.Name}};{{end}} SELECTED={{range .PinCandidates}}{{if .IsPinned}}{{.Name}};{{end}}{{end}}{{ if .CanCustomizePins }} CUSTOMIZE=1 ACTION={{.ContributionSettingsAction}} RETURN={{.ContributionSettingsReturn}}{{ end }}{{ end }}`)}, |
| 65 | "profile/follows_tab.html": {Data: []byte(`{{ define "page" }}FOLLOWTAB={{.ActiveTab}} USER={{.User.Username}} TOTAL={{len .Items}} ITEMS={{range .Items}}{{.Kind}}:{{.Username}};{{end}}{{ end }}`)}, | 65 | "profile/follows_tab.html": {Data: []byte(`{{ define "page" }}FOLLOWTAB={{.ActiveTab}} USER={{.User.Username}} TOTAL={{len .Items}} ITEMS={{range .Items}}{{.Kind}}:{{.Username}};{{end}}{{ end }}`)}, |
| 66 | "profile/suspended.html": {Data: []byte(`{{ define "page" }}SUSPENDED={{.Username}}{{ end }}`)}, | 66 | "profile/suspended.html": {Data: []byte(`{{ define "page" }}SUSPENDED={{.Username}}{{ end }}`)}, |
| 67 | "orgs/profile.html": {Data: []byte(`{{ define "page" }}ORG={{.Org.Slug}}{{ if .IsFollowing }} FOLLOWING=1{{ end }} FOLLOWERS={{.FollowerCount}} REPOS={{len .Repos}} PINS={{len .PinnedRepos}} PINNAMES={{range .PinnedRepos}}{{.Name}};{{end}} CANDIDATES={{len .PinCandidates}} SELECTED={{range .PinCandidates}}{{if .IsPinned}}{{.Name}};{{end}}{{end}} MEMBERS={{.MemberCount}} PEOPLE={{len .People}} NAMES={{range .Repos}}{{.Name}};{{end}} LANGS={{range .TopLanguages}}{{.Name}}={{.Count}};{{end}} TOPICS={{range .TopTopics}}{{.Name}}={{.Count}};{{end}} VIEWAS={{.ViewAs}}{{ if .CanCustomizePins }} CUSTOMIZE=1{{ end }}{{ end }}`)}, | 67 | "orgs/profile.html": {Data: []byte(`{{ define "page" }}ORG={{.Org.Slug}}{{ if .IsFollowing }} FOLLOWING=1{{ end }} FOLLOWERS={{.FollowerCount}} REPOS={{len .Repos}} PINS={{len .PinnedRepos}} PINNAMES={{range .PinnedRepos}}{{.Name}};{{end}} CANDIDATES={{len .PinCandidates}} SELECTED={{range .PinCandidates}}{{if .IsPinned}}{{.Name}};{{end}}{{end}} MEMBERS={{.MemberCount}} PEOPLE={{len .People}} NAMES={{range .Repos}}{{.Name}};{{end}} LANGS={{range .TopLanguages}}{{.Name}}={{.Count}};{{end}} TOPICS={{range .TopTopics}}{{.Name}}={{.Count}};{{end}} VIEWAS={{.ViewAs}}{{ if .CanCustomizePins }} CUSTOMIZE=1{{ end }}{{ end }}`)}, |
@@ -210,6 +210,15 @@ func (e *profileEnv) insertUserRepo(t *testing.T, userID int64, name, desc, visi |
| 210 | return repoID | 210 | return repoID |
| 211 | } | 211 | } |
| 212 | | 212 | |
| | 213 | +func (e *profileEnv) insertRepoCollaborator(t *testing.T, repoID, userID int64, role string) { |
| | 214 | + t.Helper() |
| | 215 | + if _, err := e.pool.Exec(context.Background(), |
| | 216 | + `INSERT INTO repo_collaborators (repo_id, user_id, role) VALUES ($1, $2, $3)`, |
| | 217 | + repoID, userID, role); err != nil { |
| | 218 | + t.Fatalf("insert repo collaborator: %v", err) |
| | 219 | + } |
| | 220 | +} |
| | 221 | + |
| 213 | func (e *profileEnv) writeInitialCommit(t *testing.T, owner, repoName, authorName, authorEmail string, when time.Time) string { | 222 | func (e *profileEnv) writeInitialCommit(t *testing.T, owner, repoName, authorName, authorEmail string, when time.Time) string { |
| 214 | t.Helper() | 223 | t.Helper() |
| 215 | if e.repoFS == nil { | 224 | if e.repoFS == nil { |
@@ -787,6 +796,58 @@ func TestProfile_UserPinsCanBeCustomized(t *testing.T) { |
| 787 | } | 796 | } |
| 788 | } | 797 | } |
| 789 | | 798 | |
| | 799 | +func TestProfile_UserPinsIncludeAffiliatedOrgAndCollaboratorRepos(t *testing.T) { |
| | 800 | + t.Parallel() |
| | 801 | + env := setupProfileEnv(t) |
| | 802 | + alice := env.insertUser(t, "alice", "Alice", "") |
| | 803 | + bob := env.insertUser(t, "bob", "Bob", "") |
| | 804 | + env.insertUserRepo(t, alice.ID, "owned", "user-owned work", "public", "Go", 0, 0) |
| | 805 | + orgID := env.insertOrg(t, "tenseleyflow", "tenseleyFlow", "workflows", alice) |
| | 806 | + orgToolID := env.insertOrgRepo(t, orgID, "org-tool", "org-owned work", "public", "Go", 2, 0) |
| | 807 | + env.insertOrgRepo(t, orgID, "secret-org-tool", "hidden org work", "private", "Rust", 0, 0) |
| | 808 | + otherOrgID := env.insertOrg(t, "strangers", "Strangers", "", bob) |
| | 809 | + strangerRepoID := env.insertOrgRepo(t, otherOrgID, "stranger-tool", "unaffiliated public repo", "public", "Go", 0, 0) |
| | 810 | + collabID := env.insertUserRepo(t, bob.ID, "collab-tool", "collaborator work", "public", "Python", 1, 0) |
| | 811 | + env.insertRepoCollaborator(t, collabID, alice.ID, "write") |
| | 812 | + |
| | 813 | + got := env.getAs(t, "/alice", alice) |
| | 814 | + for _, want := range []string{ |
| | 815 | + "CANDIDATES=3", |
| | 816 | + "alice/owned;", |
| | 817 | + "bob/collab-tool;", |
| | 818 | + "tenseleyflow/org-tool;", |
| | 819 | + } { |
| | 820 | + if !strings.Contains(got, want) { |
| | 821 | + t.Fatalf("missing %q in body: %s", want, got) |
| | 822 | + } |
| | 823 | + } |
| | 824 | + for _, notWant := range []string{"secret-org-tool", "strangers/stranger-tool"} { |
| | 825 | + if strings.Contains(got, notWant) { |
| | 826 | + t.Fatalf("unavailable repo %q was offered as a pin candidate: %s", notWant, got) |
| | 827 | + } |
| | 828 | + } |
| | 829 | + |
| | 830 | + resp := env.postPins(t, "/alice/pins", alice, orgToolID, collabID) |
| | 831 | + if resp.StatusCode != http.StatusSeeOther { |
| | 832 | + t.Fatalf("status %d, want 303", resp.StatusCode) |
| | 833 | + } |
| | 834 | + if loc := resp.Header.Get("Location"); loc != "/alice#pinned" { |
| | 835 | + t.Fatalf("Location = %q", loc) |
| | 836 | + } |
| | 837 | + |
| | 838 | + got = env.getAs(t, "/alice", alice) |
| | 839 | + for _, want := range []string{"PINS=2", "PINNAMES=org-tool;collab-tool;", "SELECTED=org-tool;collab-tool;"} { |
| | 840 | + if !strings.Contains(got, want) { |
| | 841 | + t.Fatalf("missing %q in body: %s", want, got) |
| | 842 | + } |
| | 843 | + } |
| | 844 | + |
| | 845 | + resp = env.postPins(t, "/alice/pins", alice, strangerRepoID) |
| | 846 | + if resp.StatusCode != http.StatusBadRequest { |
| | 847 | + t.Fatalf("unaffiliated repo status %d, want 400", resp.StatusCode) |
| | 848 | + } |
| | 849 | +} |
| | 850 | + |
| 790 | func TestProfile_OrgPinsFallbackUntilCustomized(t *testing.T) { | 851 | func TestProfile_OrgPinsFallbackUntilCustomized(t *testing.T) { |
| 791 | t.Parallel() | 852 | t.Parallel() |
| 792 | env := setupProfileEnv(t) | 853 | env := setupProfileEnv(t) |