Files
forge/tests/integration/org_team_invite_test.go
zeekayandHanzo Dev aa87442fdd Record upstream authorship centrally in NOTICE instead of in every file header
Source files keep their SPDX-License-Identifier and Hanzo copyright line; the
Gitea, Gogs and LFS Test Server copyrights they used to repeat now live once in
NOTICE, alongside LICENSE. The header lint accepts any copyright line above the
SPDX line, so it no longer demands an upstream one.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-10 17:50:35 -07:00

310 lines
11 KiB
Go

// Copyright 2026 Hanzo AI, Inc. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"fmt"
"net/http"
"net/url"
"strings"
"testing"
"github.com/hanzoai/git/models/organization"
"github.com/hanzoai/git/models/unittest"
user_model "github.com/hanzoai/git/models/user"
"github.com/hanzoai/git/modules/setting"
"github.com/hanzoai/git/modules/test"
"github.com/hanzoai/git/tests"
"github.com/stretchr/testify/assert"
)
func TestOrgTeamEmailInvite(t *testing.T) {
defer tests.PrepareTestEnv(t)()
org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2})
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
isMember, err := organization.IsTeamMember(t.Context(), team.OrgID, team.ID, user.ID)
assert.NoError(t, err)
assert.False(t, isMember)
session := loginUser(t, "user1")
teamURL := fmt.Sprintf("/org/%s/teams/%s", org.Name, team.Name)
req := NewRequestWithValues(t, "POST", teamURL+"/action/add", map[string]string{
"uid": "1",
"uname": user.Email,
})
resp := session.MakeRequest(t, req, http.StatusSeeOther)
req = NewRequest(t, "GET", test.RedirectURL(resp))
session.MakeRequest(t, req, http.StatusOK)
// get the invite token
invites, err := organization.GetInvitesByTeamID(t.Context(), team.ID)
assert.NoError(t, err)
assert.Len(t, invites, 1)
session = loginUser(t, user.Name)
// join the team
inviteURL := "/org/invite/" + invites[0].Token
req = NewRequest(t, "POST", inviteURL)
resp = session.MakeRequest(t, req, http.StatusSeeOther)
req = NewRequest(t, "GET", test.RedirectURL(resp))
session.MakeRequest(t, req, http.StatusOK)
isMember, err = organization.IsTeamMember(t.Context(), team.OrgID, team.ID, user.ID)
assert.NoError(t, err)
assert.True(t, isMember)
}
// Check that users are redirected to accept the invitation correctly after login
func TestOrgTeamEmailInviteRedirectsExistingUser(t *testing.T) {
defer tests.PrepareTestEnv(t)()
org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2})
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
isMember, err := organization.IsTeamMember(t.Context(), team.OrgID, team.ID, user.ID)
assert.NoError(t, err)
assert.False(t, isMember)
// create the invite
session := loginUser(t, "user1")
teamURL := fmt.Sprintf("/org/%s/teams/%s", org.Name, team.Name)
req := NewRequestWithValues(t, "POST", teamURL+"/action/add", map[string]string{
"uid": "1",
"uname": user.Email,
})
resp := session.MakeRequest(t, req, http.StatusSeeOther)
req = NewRequest(t, "GET", test.RedirectURL(resp))
session.MakeRequest(t, req, http.StatusOK)
// get the invite token
invites, err := organization.GetInvitesByTeamID(t.Context(), team.ID)
assert.NoError(t, err)
assert.Len(t, invites, 1)
// accept the invite
inviteURL := "/org/invite/" + invites[0].Token
req = NewRequest(t, "GET", "/user/login?redirect_to="+url.QueryEscape(inviteURL))
resp = MakeRequest(t, req, http.StatusOK)
req = NewRequestWithValues(t, "POST", "/user/login", map[string]string{
"user_name": "user5",
"password": "password",
})
for _, c := range resp.Result().Cookies() {
req.AddCookie(c)
}
resp = MakeRequest(t, req, http.StatusSeeOther)
assert.Equal(t, inviteURL, test.RedirectURL(resp))
// complete the login process
ch := http.Header{}
ch.Add("Cookie", strings.Join(resp.Header()["Set-Cookie"], ";"))
cr := http.Request{Header: ch}
session = emptyTestSession(t)
baseURL, err := url.Parse(setting.AppURL)
assert.NoError(t, err)
session.jar.SetCookies(baseURL, cr.Cookies())
// make the request
req = NewRequest(t, "POST", test.RedirectURL(resp))
resp = session.MakeRequest(t, req, http.StatusSeeOther)
req = NewRequest(t, "GET", test.RedirectURL(resp))
session.MakeRequest(t, req, http.StatusOK)
isMember, err = organization.IsTeamMember(t.Context(), team.OrgID, team.ID, user.ID)
assert.NoError(t, err)
assert.True(t, isMember)
}
// Check that newly signed up users are redirected to accept the invitation correctly
func TestOrgTeamEmailInviteRedirectsNewUser(t *testing.T) {
defer tests.PrepareTestEnv(t)()
org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2})
// create the invite
session := loginUser(t, "user1")
teamURL := fmt.Sprintf("/org/%s/teams/%s", org.Name, team.Name)
req := NewRequestWithValues(t, "POST", teamURL+"/action/add", map[string]string{
"uid": "1",
"uname": "[email protected]",
})
resp := session.MakeRequest(t, req, http.StatusSeeOther)
req = NewRequest(t, "GET", test.RedirectURL(resp))
session.MakeRequest(t, req, http.StatusOK)
// get the invite token
invites, err := organization.GetInvitesByTeamID(t.Context(), team.ID)
assert.NoError(t, err)
assert.Len(t, invites, 1)
// accept the invite
inviteURL := "/org/invite/" + invites[0].Token
req = NewRequest(t, "GET", "/user/sign_up?redirect_to="+url.QueryEscape(inviteURL))
resp = MakeRequest(t, req, http.StatusOK)
req = NewRequestWithValues(t, "POST", "/user/sign_up", map[string]string{
"user_name": "doesnotexist",
"email": "[email protected]",
"password": "examplePassword!1",
"retype": "examplePassword!1",
})
for _, c := range resp.Result().Cookies() {
req.AddCookie(c)
}
resp = MakeRequest(t, req, http.StatusSeeOther)
assert.Equal(t, inviteURL, test.RedirectURL(resp))
// complete the signup process
ch := http.Header{}
ch.Add("Cookie", strings.Join(resp.Header()["Set-Cookie"], ";"))
cr := http.Request{Header: ch}
session = emptyTestSession(t)
baseURL, err := url.Parse(setting.AppURL)
assert.NoError(t, err)
session.jar.SetCookies(baseURL, cr.Cookies())
// make the redirected request
req = NewRequest(t, "POST", test.RedirectURL(resp))
resp = session.MakeRequest(t, req, http.StatusSeeOther)
req = NewRequest(t, "GET", test.RedirectURL(resp))
session.MakeRequest(t, req, http.StatusOK)
// get the new user
newUser, err := user_model.GetUserByName(t.Context(), "doesnotexist")
assert.NoError(t, err)
isMember, err := organization.IsTeamMember(t.Context(), team.OrgID, team.ID, newUser.ID)
assert.NoError(t, err)
assert.True(t, isMember)
}
// Check that users are redirected correctly after confirming their email
func TestOrgTeamEmailInviteRedirectsNewUserWithActivation(t *testing.T) {
// enable email confirmation temporarily
defer test.MockVariableValue(&setting.Service.RegisterEmailConfirm, true)()
defer tests.PrepareTestEnv(t)()
org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2})
// user1: create the invite
session := loginUser(t, "user1")
teamURL := fmt.Sprintf("/org/%s/teams/%s", org.Name, team.Name)
req := NewRequestWithValues(t, "POST", teamURL+"/action/add", map[string]string{
"uid": "1",
"uname": "[email protected]",
})
resp := session.MakeRequest(t, req, http.StatusSeeOther)
req = NewRequest(t, "GET", test.RedirectURL(resp))
session.MakeRequest(t, req, http.StatusOK)
// get the invite token
invites, err := organization.GetInvitesByTeamID(t.Context(), team.ID)
assert.NoError(t, err)
assert.Len(t, invites, 1)
// new user: accept the invite
session = emptyTestSession(t)
inviteURL := "/org/invite/" + invites[0].Token
req = NewRequest(t, "GET", "/user/sign_up?redirect_to="+url.QueryEscape(inviteURL))
session.MakeRequest(t, req, http.StatusOK)
req = NewRequestWithValues(t, "POST", "/user/sign_up", map[string]string{
"user_name": "doesnotexist",
"email": "[email protected]",
"password": "examplePassword!1",
"retype": "examplePassword!1",
})
session.MakeRequest(t, req, http.StatusOK)
user, err := user_model.GetUserByName(t.Context(), "doesnotexist")
assert.NoError(t, err)
activationCode := user_model.GenerateUserTimeLimitCode(&user_model.TimeLimitCodeOptions{Purpose: user_model.TimeLimitCodeActivateAccount}, user)
activateURL := "/user/activate?code=" + activationCode
req = NewRequestWithValues(t, "POST", activateURL, map[string]string{
"password": "examplePassword!1",
})
resp = session.MakeRequest(t, req, http.StatusSeeOther)
// should be redirected to accept the invite
assert.Equal(t, inviteURL, test.RedirectURL(resp))
req = NewRequest(t, "POST", test.RedirectURL(resp))
resp = session.MakeRequest(t, req, http.StatusSeeOther)
req = NewRequest(t, "GET", test.RedirectURL(resp))
session.MakeRequest(t, req, http.StatusOK)
isMember, err := organization.IsTeamMember(t.Context(), team.OrgID, team.ID, user.ID)
assert.NoError(t, err)
assert.True(t, isMember)
}
// Test that a logged-in user who navigates to the sign-up link is then redirected using redirect_to
// For example: an invite may have been created before the user account was created, but they may be
// accepting the invite after having created an account separately
func TestOrgTeamEmailInviteRedirectsExistingUserWithLogin(t *testing.T) {
defer tests.PrepareTestEnv(t)()
org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2})
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
isMember, err := organization.IsTeamMember(t.Context(), team.OrgID, team.ID, user.ID)
assert.NoError(t, err)
assert.False(t, isMember)
// create the invite
session := loginUser(t, "user1")
teamURL := fmt.Sprintf("/org/%s/teams/%s", org.Name, team.Name)
req := NewRequestWithValues(t, "POST", teamURL+"/action/add", map[string]string{
"uid": "1",
"uname": user.Email,
})
resp := session.MakeRequest(t, req, http.StatusSeeOther)
req = NewRequest(t, "GET", test.RedirectURL(resp))
session.MakeRequest(t, req, http.StatusOK)
// get the invite token
invites, err := organization.GetInvitesByTeamID(t.Context(), team.ID)
assert.NoError(t, err)
assert.Len(t, invites, 1)
// note: the invited user has logged in
session = loginUser(t, "user5")
// accept the invite (note: this uses the sign_up url)
inviteURL := "/org/invite/" + invites[0].Token
req = NewRequest(t, "GET", "/user/sign_up?redirect_to="+url.QueryEscape(inviteURL))
resp = session.MakeRequest(t, req, http.StatusSeeOther)
assert.Equal(t, inviteURL, test.RedirectURL(resp))
// make the request
req = NewRequest(t, "POST", test.RedirectURL(resp))
resp = session.MakeRequest(t, req, http.StatusSeeOther)
req = NewRequest(t, "GET", test.RedirectURL(resp))
session.MakeRequest(t, req, http.StatusOK)
isMember, err = organization.IsTeamMember(t.Context(), team.OrgID, team.ID, user.ID)
assert.NoError(t, err)
assert.True(t, isMember)
}