2022-01-20 13:46:51 +00:00
package coderd_test
import (
"context"
2022-04-22 20:27:55 +00:00
"fmt"
2022-01-25 01:09:39 +00:00
"net/http"
2022-05-27 20:47:03 +00:00
"strings"
2022-01-20 13:46:51 +00:00
"testing"
2022-06-01 19:58:55 +00:00
"time"
2022-01-20 13:46:51 +00:00
2023-08-25 19:34:07 +00:00
"github.com/coder/coder/v2/coderd"
"github.com/coder/coder/v2/coderd/coderdtest/oidctest"
2024-03-15 16:24:38 +00:00
"github.com/coder/serpent"
2023-08-25 19:34:07 +00:00
"github.com/golang-jwt/jwt/v4"
2022-03-07 17:40:54 +00:00
"github.com/google/uuid"
2023-05-19 00:54:45 +00:00
"github.com/stretchr/testify/assert"
2022-01-20 16:00:13 +00:00
"github.com/stretchr/testify/require"
2023-08-09 19:50:26 +00:00
"golang.org/x/exp/slices"
2022-08-09 17:17:00 +00:00
"golang.org/x/sync/errgroup"
2022-01-20 16:00:13 +00:00
2023-08-18 18:55:43 +00:00
"github.com/coder/coder/v2/coderd/audit"
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbauthz"
2024-01-30 22:02:21 +00:00
"github.com/coder/coder/v2/coderd/database/dbfake"
2023-12-04 16:05:17 +00:00
"github.com/coder/coder/v2/coderd/database/dbgen"
2023-09-01 16:50:12 +00:00
"github.com/coder/coder/v2/coderd/database/dbtime"
2023-08-18 18:55:43 +00:00
"github.com/coder/coder/v2/coderd/rbac"
"github.com/coder/coder/v2/coderd/util/slice"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/testutil"
2022-01-20 13:46:51 +00:00
)
2022-03-07 17:40:54 +00:00
func TestFirstUser ( t * testing . T ) {
2022-02-10 14:33:27 +00:00
t . Parallel ( )
2022-03-07 17:40:54 +00:00
t . Run ( "BadRequest" , func ( t * testing . T ) {
2022-02-10 14:33:27 +00:00
t . Parallel ( )
2022-02-21 20:36:29 +00:00
client := coderdtest . New ( t , nil )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
2022-09-20 04:11:01 +00:00
has , err := client . HasFirstUser ( context . Background ( ) )
require . NoError ( t , err )
require . False ( t , has )
_ , err = client . CreateFirstUser ( ctx , codersdk . CreateFirstUserRequest { } )
2022-03-07 17:40:54 +00:00
require . Error ( t , err )
2022-02-10 14:33:27 +00:00
} )
2022-03-07 17:40:54 +00:00
t . Run ( "AlreadyExists" , func ( t * testing . T ) {
2022-02-10 14:33:27 +00:00
t . Parallel ( )
2022-02-21 20:36:29 +00:00
client := coderdtest . New ( t , nil )
2022-03-07 17:40:54 +00:00
_ = coderdtest . CreateFirstUser ( t , client )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
_ , err := client . CreateFirstUser ( ctx , codersdk . CreateFirstUserRequest {
2022-11-16 23:09:49 +00:00
Email : "some@email.com" ,
Username : "exampleuser" ,
2023-02-08 20:10:08 +00:00
Password : "SomeSecurePassword!" ,
2022-03-07 17:40:54 +00:00
} )
var apiErr * codersdk . Error
require . ErrorAs ( t , err , & apiErr )
require . Equal ( t , http . StatusConflict , apiErr . StatusCode ( ) )
} )
t . Run ( "Create" , func ( t * testing . T ) {
t . Parallel ( )
client := coderdtest . New ( t , nil )
_ = coderdtest . CreateFirstUser ( t , client )
2022-02-10 14:33:27 +00:00
} )
2022-08-25 19:32:35 +00:00
2022-11-16 23:09:49 +00:00
t . Run ( "Trial" , func ( t * testing . T ) {
t . Parallel ( )
2024-02-23 16:48:24 +00:00
trialGenerated := make ( chan struct { } )
entitlementsRefreshed := make ( chan struct { } )
2022-11-16 23:09:49 +00:00
client := coderdtest . New ( t , & coderdtest . Options {
2024-01-17 00:19:16 +00:00
TrialGenerator : func ( context . Context , codersdk . LicensorTrialRequest ) error {
2024-02-23 16:48:24 +00:00
close ( trialGenerated )
return nil
} ,
RefreshEntitlements : func ( context . Context ) error {
close ( entitlementsRefreshed )
2022-11-16 23:09:49 +00:00
return nil
} ,
} )
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
req := codersdk . CreateFirstUserRequest {
Email : "testuser@coder.com" ,
Username : "testuser" ,
2023-02-08 20:10:08 +00:00
Password : "SomeSecurePassword!" ,
2022-11-16 23:09:49 +00:00
Trial : true ,
}
_ , err := client . CreateFirstUser ( ctx , req )
require . NoError ( t , err )
2024-02-23 16:48:24 +00:00
_ = testutil . RequireRecvCtx ( ctx , t , trialGenerated )
_ = testutil . RequireRecvCtx ( ctx , t , entitlementsRefreshed )
2022-11-16 23:09:49 +00:00
} )
2022-02-10 14:33:27 +00:00
}
2022-03-07 17:40:54 +00:00
func TestPostLogin ( t * testing . T ) {
2022-01-20 13:46:51 +00:00
t . Parallel ( )
2022-03-07 17:40:54 +00:00
t . Run ( "InvalidUser" , func ( t * testing . T ) {
2022-01-20 13:46:51 +00:00
t . Parallel ( )
2023-03-22 19:52:13 +00:00
client := coderdtest . New ( t , nil )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
_ , err := client . LoginWithPassword ( ctx , codersdk . LoginWithPasswordRequest {
2022-03-07 17:40:54 +00:00
Email : "my@email.org" ,
Password : "password" ,
} )
var apiErr * codersdk . Error
require . ErrorAs ( t , err , & apiErr )
require . Equal ( t , http . StatusUnauthorized , apiErr . StatusCode ( ) )
2022-01-20 13:46:51 +00:00
} )
2022-03-07 17:40:54 +00:00
t . Run ( "BadPassword" , func ( t * testing . T ) {
2022-01-20 13:46:51 +00:00
t . Parallel ( )
2023-02-06 20:12:50 +00:00
auditor := audit . NewMock ( )
client := coderdtest . New ( t , & coderdtest . Options { Auditor : auditor } )
2023-03-30 17:13:03 +00:00
numLogs := len ( auditor . AuditLogs ( ) )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
2022-03-22 19:17:50 +00:00
req := codersdk . CreateFirstUserRequest {
2022-11-16 23:09:49 +00:00
Email : "testuser@coder.com" ,
Username : "testuser" ,
2023-02-08 20:10:08 +00:00
Password : "SomeSecurePassword!" ,
2022-03-07 17:40:54 +00:00
}
2022-08-09 17:17:00 +00:00
_ , err := client . CreateFirstUser ( ctx , req )
2022-03-07 17:40:54 +00:00
require . NoError ( t , err )
2022-08-09 17:17:00 +00:00
_ , err = client . LoginWithPassword ( ctx , codersdk . LoginWithPasswordRequest {
2022-03-07 17:40:54 +00:00
Email : req . Email ,
Password : "badpass" ,
2022-01-20 13:46:51 +00:00
} )
2023-02-06 20:12:50 +00:00
numLogs ++ // add an audit log for login
2022-02-06 00:24:51 +00:00
var apiErr * codersdk . Error
require . ErrorAs ( t , err , & apiErr )
2022-03-07 17:40:54 +00:00
require . Equal ( t , http . StatusUnauthorized , apiErr . StatusCode ( ) )
2023-02-06 20:12:50 +00:00
2023-03-30 17:13:03 +00:00
require . Len ( t , auditor . AuditLogs ( ) , numLogs )
require . Equal ( t , database . AuditActionLogin , auditor . AuditLogs ( ) [ numLogs - 1 ] . Action )
2022-01-20 13:46:51 +00:00
} )
2022-05-31 13:06:42 +00:00
t . Run ( "Suspended" , func ( t * testing . T ) {
t . Parallel ( )
2023-02-06 20:12:50 +00:00
auditor := audit . NewMock ( )
client := coderdtest . New ( t , & coderdtest . Options { Auditor : auditor } )
2023-03-30 17:13:03 +00:00
numLogs := len ( auditor . AuditLogs ( ) )
2022-05-31 13:06:42 +00:00
first := coderdtest . CreateFirstUser ( t , client )
2023-02-06 20:12:50 +00:00
numLogs ++ // add an audit log for create user
numLogs ++ // add an audit log for login
2022-05-31 13:06:42 +00:00
2023-02-06 23:48:21 +00:00
member , _ := coderdtest . CreateAnotherUser ( t , client , first . OrganizationID )
2023-02-06 20:12:50 +00:00
numLogs ++ // add an audit log for create user
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
memberUser , err := member . User ( ctx , codersdk . Me )
2022-05-31 13:06:42 +00:00
require . NoError ( t , err , "fetch member user" )
2022-08-09 17:17:00 +00:00
_ , err = client . UpdateUserStatus ( ctx , memberUser . Username , codersdk . UserStatusSuspended )
2022-05-31 13:06:42 +00:00
require . NoError ( t , err , "suspend member" )
2023-02-06 20:12:50 +00:00
numLogs ++ // add an audit log for update user
2022-05-31 13:06:42 +00:00
// Test an existing session
2022-08-09 17:17:00 +00:00
_ , err = member . User ( ctx , codersdk . Me )
2022-05-31 13:06:42 +00:00
var apiErr * codersdk . Error
require . ErrorAs ( t , err , & apiErr )
require . Equal ( t , http . StatusUnauthorized , apiErr . StatusCode ( ) )
2022-06-03 21:48:09 +00:00
require . Contains ( t , apiErr . Message , "Contact an admin" )
2022-05-31 13:06:42 +00:00
// Test a new session
2022-08-09 17:17:00 +00:00
_ , err = client . LoginWithPassword ( ctx , codersdk . LoginWithPasswordRequest {
2022-05-31 13:06:42 +00:00
Email : memberUser . Email ,
2023-02-08 20:10:08 +00:00
Password : "SomeSecurePassword!" ,
2022-05-31 13:06:42 +00:00
} )
2023-02-06 20:12:50 +00:00
numLogs ++ // add an audit log for login
2022-05-31 13:06:42 +00:00
require . ErrorAs ( t , err , & apiErr )
require . Equal ( t , http . StatusUnauthorized , apiErr . StatusCode ( ) )
require . Contains ( t , apiErr . Message , "suspended" )
2023-02-06 20:12:50 +00:00
2023-03-30 17:13:03 +00:00
require . Len ( t , auditor . AuditLogs ( ) , numLogs )
require . Equal ( t , database . AuditActionLogin , auditor . AuditLogs ( ) [ numLogs - 1 ] . Action )
2022-05-31 13:06:42 +00:00
} )
2023-02-06 14:58:21 +00:00
t . Run ( "DisabledPasswordAuth" , func ( t * testing . T ) {
t . Parallel ( )
2023-03-07 21:10:01 +00:00
dc := coderdtest . DeploymentValues ( t )
2023-02-06 14:58:21 +00:00
client := coderdtest . New ( t , & coderdtest . Options {
2023-03-07 21:10:01 +00:00
DeploymentValues : dc ,
2023-02-06 14:58:21 +00:00
} )
first := coderdtest . CreateFirstUser ( t , client )
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
// With a user account.
2023-02-08 20:10:08 +00:00
const password = "SomeSecurePassword!"
2023-02-06 14:58:21 +00:00
user , err := client . CreateUser ( ctx , codersdk . CreateUserRequest {
Email : "test+user-@coder.com" ,
Username : "user" ,
Password : password ,
OrganizationID : first . OrganizationID ,
} )
require . NoError ( t , err )
2024-03-15 16:24:38 +00:00
dc . DisablePasswordAuth = serpent . Bool ( true )
2023-02-14 14:58:12 +00:00
2023-02-06 14:58:21 +00:00
userClient := codersdk . New ( client . URL )
_ , err = userClient . LoginWithPassword ( ctx , codersdk . LoginWithPasswordRequest {
Email : user . Email ,
Password : password ,
} )
require . Error ( t , err )
var apiErr * codersdk . Error
require . ErrorAs ( t , err , & apiErr )
require . Equal ( t , http . StatusForbidden , apiErr . StatusCode ( ) )
require . Contains ( t , apiErr . Message , "Password authentication is disabled" )
} )
2022-03-07 17:40:54 +00:00
t . Run ( "Success" , func ( t * testing . T ) {
2022-01-24 17:07:42 +00:00
t . Parallel ( )
2023-02-06 20:12:50 +00:00
auditor := audit . NewMock ( )
client := coderdtest . New ( t , & coderdtest . Options { Auditor : auditor } )
2023-03-30 17:13:03 +00:00
numLogs := len ( auditor . AuditLogs ( ) )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
2022-03-22 19:17:50 +00:00
req := codersdk . CreateFirstUserRequest {
2022-11-16 23:09:49 +00:00
Email : "testuser@coder.com" ,
Username : "testuser" ,
2023-02-08 20:10:08 +00:00
Password : "SomeSecurePassword!" ,
2022-03-07 17:40:54 +00:00
}
2022-08-09 17:17:00 +00:00
_ , err := client . CreateFirstUser ( ctx , req )
2022-03-07 17:40:54 +00:00
require . NoError ( t , err )
2023-02-06 20:12:50 +00:00
numLogs ++ // add an audit log for create user
numLogs ++ // add an audit log for login
2022-09-27 03:51:58 +00:00
2022-08-09 17:17:00 +00:00
_ , err = client . LoginWithPassword ( ctx , codersdk . LoginWithPasswordRequest {
2022-03-07 17:40:54 +00:00
Email : req . Email ,
Password : req . Password ,
} )
require . NoError ( t , err )
2022-09-27 03:51:58 +00:00
// Login should be case insensitive
_ , err = client . LoginWithPassword ( ctx , codersdk . LoginWithPasswordRequest {
Email : strings . ToUpper ( req . Email ) ,
Password : req . Password ,
} )
require . NoError ( t , err )
2023-02-06 20:12:50 +00:00
2023-03-30 17:13:03 +00:00
require . Len ( t , auditor . AuditLogs ( ) , numLogs )
require . Equal ( t , database . AuditActionLogin , auditor . AuditLogs ( ) [ numLogs - 1 ] . Action )
2022-03-07 17:40:54 +00:00
} )
2022-06-01 19:58:55 +00:00
t . Run ( "Lifetime&Expire" , func ( t * testing . T ) {
t . Parallel ( )
2022-08-09 17:17:00 +00:00
2022-06-27 18:50:52 +00:00
client := coderdtest . New ( t , nil )
2023-10-10 10:14:20 +00:00
owner := coderdtest . CreateFirstUser ( t , client )
2022-06-01 19:58:55 +00:00
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
2022-11-09 13:31:24 +00:00
split := strings . Split ( client . SessionToken ( ) , "-" )
2023-10-10 10:14:20 +00:00
key , err := client . APIKeyByID ( ctx , owner . UserID . String ( ) , split [ 0 ] )
2022-06-01 19:58:55 +00:00
require . NoError ( t , err , "fetch login key" )
2022-06-27 18:50:52 +00:00
require . Equal ( t , int64 ( 86400 ) , key . LifetimeSeconds , "default should be 86400" )
2022-06-01 19:58:55 +00:00
2022-10-06 19:02:27 +00:00
// tokens have a longer life
2022-10-14 16:46:38 +00:00
token , err := client . CreateToken ( ctx , codersdk . Me , codersdk . CreateTokenRequest { } )
2022-10-06 19:02:27 +00:00
require . NoError ( t , err , "make new token api key" )
2022-06-01 19:58:55 +00:00
split = strings . Split ( token . Key , "-" )
2023-10-10 10:14:20 +00:00
apiKey , err := client . APIKeyByID ( ctx , owner . UserID . String ( ) , split [ 0 ] )
2022-06-01 19:58:55 +00:00
require . NoError ( t , err , "fetch api key" )
2022-12-12 20:39:31 +00:00
require . True ( t , apiKey . ExpiresAt . After ( time . Now ( ) . Add ( time . Hour * 24 * 29 ) ) , "default tokens lasts more than 29 days" )
require . True ( t , apiKey . ExpiresAt . Before ( time . Now ( ) . Add ( time . Hour * 24 * 31 ) ) , "default tokens lasts less than 31 days" )
2022-10-06 19:02:27 +00:00
require . Greater ( t , apiKey . LifetimeSeconds , key . LifetimeSeconds , "token should have longer lifetime" )
2022-06-01 19:58:55 +00:00
} )
2022-03-07 17:40:54 +00:00
}
2022-09-12 23:24:20 +00:00
func TestDeleteUser ( t * testing . T ) {
t . Parallel ( )
t . Run ( "Works" , func ( t * testing . T ) {
t . Parallel ( )
2023-03-21 14:10:22 +00:00
client , _ , api := coderdtest . NewWithAPI ( t , nil )
user := coderdtest . CreateFirstUser ( t , client )
authz := coderdtest . AssertRBAC ( t , api , client )
2023-04-24 20:48:26 +00:00
anotherClient , another := coderdtest . CreateAnotherUser ( t , client , user . OrganizationID )
2023-03-21 14:10:22 +00:00
err := client . DeleteUser ( context . Background ( ) , another . ID )
2022-09-12 23:24:20 +00:00
require . NoError ( t , err )
// Attempt to create a user with the same email and username, and delete them again.
2023-03-21 14:10:22 +00:00
another , err = client . CreateUser ( context . Background ( ) , codersdk . CreateUserRequest {
2022-09-12 23:24:20 +00:00
Email : another . Email ,
Username : another . Username ,
2023-02-08 20:10:08 +00:00
Password : "SomeSecurePassword!" ,
2022-09-12 23:24:20 +00:00
OrganizationID : user . OrganizationID ,
} )
require . NoError ( t , err )
2023-03-21 14:10:22 +00:00
err = client . DeleteUser ( context . Background ( ) , another . ID )
2022-09-12 23:24:20 +00:00
require . NoError ( t , err )
2023-03-21 14:10:22 +00:00
2023-04-24 20:48:26 +00:00
// IMPORTANT: assert that the deleted user's session is no longer valid.
_ , err = anotherClient . User ( context . Background ( ) , codersdk . Me )
require . Error ( t , err )
var apiErr * codersdk . Error
require . ErrorAs ( t , err , & apiErr )
require . Equal ( t , http . StatusUnauthorized , apiErr . StatusCode ( ) )
2023-03-21 14:10:22 +00:00
// RBAC checks
authz . AssertChecked ( t , rbac . ActionCreate , rbac . ResourceUser )
authz . AssertChecked ( t , rbac . ActionDelete , another )
2022-09-12 23:24:20 +00:00
} )
t . Run ( "NoPermission" , func ( t * testing . T ) {
t . Parallel ( )
api := coderdtest . New ( t , nil )
firstUser := coderdtest . CreateFirstUser ( t , api )
2023-02-06 23:48:21 +00:00
client , _ := coderdtest . CreateAnotherUser ( t , api , firstUser . OrganizationID )
2022-09-12 23:24:20 +00:00
err := client . DeleteUser ( context . Background ( ) , firstUser . UserID )
var apiErr * codersdk . Error
require . ErrorAs ( t , err , & apiErr )
2023-10-11 05:41:14 +00:00
require . Equal ( t , http . StatusBadRequest , apiErr . StatusCode ( ) )
2022-09-12 23:24:20 +00:00
} )
t . Run ( "HasWorkspaces" , func ( t * testing . T ) {
t . Parallel ( )
client , _ := coderdtest . NewWithProvisionerCloser ( t , nil )
user := coderdtest . CreateFirstUser ( t , client )
2023-02-06 23:48:21 +00:00
anotherClient , another := coderdtest . CreateAnotherUser ( t , client , user . OrganizationID )
2022-09-12 23:24:20 +00:00
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , nil )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2022-09-12 23:24:20 +00:00
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
coderdtest . CreateWorkspace ( t , anotherClient , user . OrganizationID , template . ID )
err := client . DeleteUser ( context . Background ( ) , another . ID )
var apiErr * codersdk . Error
require . ErrorAs ( t , err , & apiErr )
require . Equal ( t , http . StatusExpectationFailed , apiErr . StatusCode ( ) )
} )
2023-02-22 16:48:16 +00:00
t . Run ( "Self" , func ( t * testing . T ) {
t . Parallel ( )
client := coderdtest . New ( t , nil )
user := coderdtest . CreateFirstUser ( t , client )
err := client . DeleteUser ( context . Background ( ) , user . UserID )
var apiErr * codersdk . Error
require . Error ( t , err , "should not be able to delete self" )
require . ErrorAs ( t , err , & apiErr , "should be a coderd error" )
require . Equal ( t , http . StatusForbidden , apiErr . StatusCode ( ) , "should be forbidden" )
} )
2022-09-12 23:24:20 +00:00
}
2022-03-07 17:40:54 +00:00
func TestPostLogout ( t * testing . T ) {
t . Parallel ( )
2022-05-27 20:47:03 +00:00
// Checks that the cookie is cleared and the API Key is deleted from the database.
t . Run ( "Logout" , func ( t * testing . T ) {
2022-03-07 17:40:54 +00:00
t . Parallel ( )
2023-02-06 20:12:50 +00:00
auditor := audit . NewMock ( )
client := coderdtest . New ( t , & coderdtest . Options { Auditor : auditor } )
2023-03-30 17:13:03 +00:00
numLogs := len ( auditor . AuditLogs ( ) )
2022-03-07 17:40:54 +00:00
2023-10-10 10:14:20 +00:00
owner := coderdtest . CreateFirstUser ( t , client )
2023-02-06 20:12:50 +00:00
numLogs ++ // add an audit log for login
2022-05-27 20:47:03 +00:00
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
2022-11-09 13:31:24 +00:00
keyID := strings . Split ( client . SessionToken ( ) , "-" ) [ 0 ]
2023-10-10 10:14:20 +00:00
apiKey , err := client . APIKeyByID ( ctx , owner . UserID . String ( ) , keyID )
2022-05-27 20:47:03 +00:00
require . NoError ( t , err )
require . Equal ( t , keyID , apiKey . ID , "API key should exist in the database" )
2022-03-07 17:40:54 +00:00
fullURL , err := client . URL . Parse ( "/api/v2/users/logout" )
require . NoError ( t , err , "Server URL should parse successfully" )
2022-05-27 20:47:03 +00:00
res , err := client . Request ( ctx , http . MethodPost , fullURL . String ( ) , nil )
2023-02-06 20:12:50 +00:00
numLogs ++ // add an audit log for logout
2022-05-27 20:47:03 +00:00
require . NoError ( t , err , "/logout request should succeed" )
res . Body . Close ( )
require . Equal ( t , http . StatusOK , res . StatusCode )
2022-03-07 17:40:54 +00:00
2023-03-30 17:13:03 +00:00
require . Len ( t , auditor . AuditLogs ( ) , numLogs )
require . Equal ( t , database . AuditActionLogout , auditor . AuditLogs ( ) [ numLogs - 1 ] . Action )
2023-02-06 20:12:50 +00:00
2022-05-27 20:47:03 +00:00
cookies := res . Cookies ( )
2022-09-13 19:26:46 +00:00
var found bool
for _ , cookie := range cookies {
2023-01-29 21:47:24 +00:00
if cookie . Name == codersdk . SessionTokenCookie {
require . Equal ( t , codersdk . SessionTokenCookie , cookie . Name , "Cookie should be the auth cookie" )
2022-09-13 19:26:46 +00:00
require . Equal ( t , - 1 , cookie . MaxAge , "Cookie should be set to delete" )
found = true
}
}
require . True ( t , found , "auth cookie should be returned" )
2022-03-07 17:40:54 +00:00
2023-10-10 10:14:20 +00:00
_ , err = client . APIKeyByID ( ctx , owner . UserID . String ( ) , keyID )
2022-08-09 17:17:00 +00:00
sdkErr := & codersdk . Error { }
2022-06-27 18:50:52 +00:00
require . ErrorAs ( t , err , & sdkErr )
require . Equal ( t , http . StatusUnauthorized , sdkErr . StatusCode ( ) , "Expecting 401" )
2022-01-23 05:58:10 +00:00
} )
2022-02-06 00:24:51 +00:00
}
2022-01-23 05:58:10 +00:00
2023-08-25 19:34:07 +00:00
// nolint:bodyclose
2022-02-06 00:24:51 +00:00
func TestPostUsers ( t * testing . T ) {
t . Parallel ( )
2022-03-07 17:40:54 +00:00
t . Run ( "NoAuth" , func ( t * testing . T ) {
2022-01-20 13:46:51 +00:00
t . Parallel ( )
2022-02-21 20:36:29 +00:00
client := coderdtest . New ( t , nil )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
_ , err := client . CreateUser ( ctx , codersdk . CreateUserRequest { } )
2022-01-20 13:46:51 +00:00
require . Error ( t , err )
} )
2022-02-06 00:24:51 +00:00
t . Run ( "Conflicting" , func ( t * testing . T ) {
2022-01-20 13:46:51 +00:00
t . Parallel ( )
2022-02-21 20:36:29 +00:00
client := coderdtest . New ( t , nil )
2022-03-07 17:40:54 +00:00
coderdtest . CreateFirstUser ( t , client )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
me , err := client . User ( ctx , codersdk . Me )
2022-03-07 17:40:54 +00:00
require . NoError ( t , err )
2022-08-09 17:17:00 +00:00
_ , err = client . CreateUser ( ctx , codersdk . CreateUserRequest {
2022-03-07 17:40:54 +00:00
Email : me . Email ,
Username : me . Username ,
2023-02-08 20:10:08 +00:00
Password : "MySecurePassword!" ,
2022-04-01 19:42:36 +00:00
OrganizationID : uuid . New ( ) ,
2022-01-20 13:46:51 +00:00
} )
2022-02-06 00:24:51 +00:00
var apiErr * codersdk . Error
require . ErrorAs ( t , err , & apiErr )
require . Equal ( t , http . StatusConflict , apiErr . StatusCode ( ) )
2022-01-20 13:46:51 +00:00
} )
2022-01-23 05:58:10 +00:00
2022-03-07 17:40:54 +00:00
t . Run ( "OrganizationNotFound" , func ( t * testing . T ) {
t . Parallel ( )
client := coderdtest . New ( t , nil )
coderdtest . CreateFirstUser ( t , client )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
_ , err := client . CreateUser ( ctx , codersdk . CreateUserRequest {
2022-04-01 19:42:36 +00:00
OrganizationID : uuid . New ( ) ,
2022-03-07 17:40:54 +00:00
Email : "another@user.org" ,
Username : "someone-else" ,
2023-02-08 20:10:08 +00:00
Password : "SomeSecurePassword!" ,
2022-03-07 17:40:54 +00:00
} )
var apiErr * codersdk . Error
require . ErrorAs ( t , err , & apiErr )
require . Equal ( t , http . StatusNotFound , apiErr . StatusCode ( ) )
} )
t . Run ( "OrganizationNoAccess" , func ( t * testing . T ) {
t . Parallel ( )
client := coderdtest . New ( t , nil )
first := coderdtest . CreateFirstUser ( t , client )
2023-02-06 23:48:21 +00:00
notInOrg , _ := coderdtest . CreateAnotherUser ( t , client , first . OrganizationID )
other , _ := coderdtest . CreateAnotherUser ( t , client , first . OrganizationID , rbac . RoleOwner ( ) , rbac . RoleMember ( ) )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
org , err := other . CreateOrganization ( ctx , codersdk . CreateOrganizationRequest {
2022-03-07 17:40:54 +00:00
Name : "another" ,
} )
require . NoError ( t , err )
2022-08-09 17:17:00 +00:00
_ , err = notInOrg . CreateUser ( ctx , codersdk . CreateUserRequest {
2022-03-07 17:40:54 +00:00
Email : "some@domain.com" ,
Username : "anotheruser" ,
2023-02-08 20:10:08 +00:00
Password : "SomeSecurePassword!" ,
2022-03-07 17:40:54 +00:00
OrganizationID : org . ID ,
} )
var apiErr * codersdk . Error
require . ErrorAs ( t , err , & apiErr )
2023-03-21 14:10:22 +00:00
require . Equal ( t , http . StatusNotFound , apiErr . StatusCode ( ) )
2022-03-07 17:40:54 +00:00
} )
2023-05-19 00:54:45 +00:00
t . Run ( "CreateWithoutOrg" , func ( t * testing . T ) {
t . Parallel ( )
auditor := audit . NewMock ( )
client := coderdtest . New ( t , & coderdtest . Options { Auditor : auditor } )
firstUser := coderdtest . CreateFirstUser ( t , client )
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
2024-02-16 14:28:36 +00:00
// Add an extra org to try and confuse user creation
_ , err := client . CreateOrganization ( ctx , codersdk . CreateOrganizationRequest {
Name : "foobar" ,
} )
require . NoError ( t , err )
numLogs := len ( auditor . AuditLogs ( ) )
2023-05-19 00:54:45 +00:00
user , err := client . CreateUser ( ctx , codersdk . CreateUserRequest {
Email : "another@user.org" ,
Username : "someone-else" ,
Password : "SomeSecurePassword!" ,
} )
require . NoError ( t , err )
2024-02-16 14:28:36 +00:00
numLogs ++ // add an audit log for user create
2023-05-19 00:54:45 +00:00
require . Len ( t , auditor . AuditLogs ( ) , numLogs )
require . Equal ( t , database . AuditActionCreate , auditor . AuditLogs ( ) [ numLogs - 1 ] . Action )
require . Equal ( t , database . AuditActionLogin , auditor . AuditLogs ( ) [ numLogs - 2 ] . Action )
require . Len ( t , user . OrganizationIDs , 1 )
assert . Equal ( t , firstUser . OrganizationID , user . OrganizationIDs [ 0 ] )
} )
2022-02-06 00:24:51 +00:00
t . Run ( "Create" , func ( t * testing . T ) {
2022-01-23 05:58:10 +00:00
t . Parallel ( )
2022-09-09 02:16:16 +00:00
auditor := audit . NewMock ( )
client := coderdtest . New ( t , & coderdtest . Options { Auditor : auditor } )
2023-03-30 17:13:03 +00:00
numLogs := len ( auditor . AuditLogs ( ) )
2023-02-06 20:12:50 +00:00
2023-05-19 00:54:45 +00:00
firstUser := coderdtest . CreateFirstUser ( t , client )
2023-02-06 20:12:50 +00:00
numLogs ++ // add an audit log for user create
numLogs ++ // add an audit log for login
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
2023-05-19 00:54:45 +00:00
user , err := client . CreateUser ( ctx , codersdk . CreateUserRequest {
OrganizationID : firstUser . OrganizationID ,
2022-03-07 17:40:54 +00:00
Email : "another@user.org" ,
Username : "someone-else" ,
2023-02-08 20:10:08 +00:00
Password : "SomeSecurePassword!" ,
2022-02-06 00:24:51 +00:00
} )
2022-01-23 05:58:10 +00:00
require . NoError ( t , err )
2022-09-10 16:07:45 +00:00
2023-03-30 17:13:03 +00:00
require . Len ( t , auditor . AuditLogs ( ) , numLogs )
require . Equal ( t , database . AuditActionCreate , auditor . AuditLogs ( ) [ numLogs - 1 ] . Action )
require . Equal ( t , database . AuditActionLogin , auditor . AuditLogs ( ) [ numLogs - 2 ] . Action )
2023-05-19 00:54:45 +00:00
require . Len ( t , user . OrganizationIDs , 1 )
assert . Equal ( t , firstUser . OrganizationID , user . OrganizationIDs [ 0 ] )
2022-01-23 05:58:10 +00:00
} )
2023-02-06 14:58:21 +00:00
t . Run ( "LastSeenAt" , func ( t * testing . T ) {
t . Parallel ( )
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
client := coderdtest . New ( t , nil )
firstUserResp := coderdtest . CreateFirstUser ( t , client )
firstUser , err := client . User ( ctx , firstUserResp . UserID . String ( ) )
require . NoError ( t , err )
2023-02-06 23:48:21 +00:00
_ , _ = coderdtest . CreateAnotherUser ( t , client , firstUserResp . OrganizationID )
2023-02-06 14:58:21 +00:00
allUsersRes , err := client . Users ( ctx , codersdk . UsersRequest { } )
require . NoError ( t , err )
require . Len ( t , allUsersRes . Users , 2 )
// We sent the "GET Users" request with the first user, but the second user
// should be Never since they haven't performed a request.
for _ , user := range allUsersRes . Users {
if user . ID == firstUser . ID {
2023-09-01 16:50:12 +00:00
require . WithinDuration ( t , firstUser . LastSeenAt , dbtime . Now ( ) , testutil . WaitShort )
2023-02-06 14:58:21 +00:00
} else {
require . Zero ( t , user . LastSeenAt )
}
}
} )
2023-08-11 01:04:35 +00:00
t . Run ( "CreateNoneLoginType" , func ( t * testing . T ) {
t . Parallel ( )
client := coderdtest . New ( t , nil )
first := coderdtest . CreateFirstUser ( t , client )
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
user , err := client . CreateUser ( ctx , codersdk . CreateUserRequest {
OrganizationID : first . OrganizationID ,
Email : "another@user.org" ,
Username : "someone-else" ,
Password : "" ,
UserLoginType : codersdk . LoginTypeNone ,
} )
require . NoError ( t , err )
found , err := client . User ( ctx , user . ID . String ( ) )
require . NoError ( t , err )
require . Equal ( t , found . LoginType , codersdk . LoginTypeNone )
} )
t . Run ( "CreateOIDCLoginType" , func ( t * testing . T ) {
t . Parallel ( )
email := "another@user.org"
2023-08-25 19:34:07 +00:00
fake := oidctest . NewFakeIDP ( t ,
oidctest . WithServing ( ) ,
)
cfg := fake . OIDCConfig ( t , nil , func ( cfg * coderd . OIDCConfig ) {
cfg . AllowSignups = true
2023-08-11 01:04:35 +00:00
} )
client := coderdtest . New ( t , & coderdtest . Options {
2023-08-25 19:34:07 +00:00
OIDCConfig : cfg ,
2023-08-11 01:04:35 +00:00
} )
first := coderdtest . CreateFirstUser ( t , client )
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
_ , err := client . CreateUser ( ctx , codersdk . CreateUserRequest {
OrganizationID : first . OrganizationID ,
Email : email ,
Username : "someone-else" ,
Password : "" ,
UserLoginType : codersdk . LoginTypeOIDC ,
} )
require . NoError ( t , err )
// Try to log in with OIDC.
2023-08-25 19:34:07 +00:00
userClient , _ := fake . Login ( t , client , jwt . MapClaims {
2023-08-11 01:04:35 +00:00
"email" : email ,
2023-08-25 19:34:07 +00:00
} )
2023-08-11 01:04:35 +00:00
found , err := userClient . User ( ctx , "me" )
require . NoError ( t , err )
require . Equal ( t , found . LoginType , codersdk . LoginTypeOIDC )
} )
2022-02-06 00:24:51 +00:00
}
2022-01-25 19:52:58 +00:00
2022-04-12 14:05:21 +00:00
func TestUpdateUserProfile ( t * testing . T ) {
t . Parallel ( )
t . Run ( "UserNotFound" , func ( t * testing . T ) {
t . Parallel ( )
client := coderdtest . New ( t , nil )
coderdtest . CreateFirstUser ( t , client )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
_ , err := client . UpdateUserProfile ( ctx , uuid . New ( ) . String ( ) , codersdk . UpdateUserProfileRequest {
2022-04-12 14:05:21 +00:00
Username : "newusername" ,
} )
var apiErr * codersdk . Error
require . ErrorAs ( t , err , & apiErr )
// Right now, we are raising a BAD request error because we don't support a
// user accessing other users info
require . Equal ( t , http . StatusBadRequest , apiErr . StatusCode ( ) )
} )
t . Run ( "ConflictingUsername" , func ( t * testing . T ) {
t . Parallel ( )
client := coderdtest . New ( t , nil )
user := coderdtest . CreateFirstUser ( t , client )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
existentUser , err := client . CreateUser ( ctx , codersdk . CreateUserRequest {
2022-04-12 14:05:21 +00:00
Email : "bruno@coder.com" ,
Username : "bruno" ,
2023-02-08 20:10:08 +00:00
Password : "SomeSecurePassword!" ,
2022-04-12 14:05:21 +00:00
OrganizationID : user . OrganizationID ,
} )
2022-04-23 22:58:57 +00:00
require . NoError ( t , err )
2022-08-09 17:17:00 +00:00
_ , err = client . UpdateUserProfile ( ctx , codersdk . Me , codersdk . UpdateUserProfileRequest {
2022-04-12 14:05:21 +00:00
Username : existentUser . Username ,
} )
var apiErr * codersdk . Error
require . ErrorAs ( t , err , & apiErr )
require . Equal ( t , http . StatusConflict , apiErr . StatusCode ( ) )
} )
2024-01-17 12:20:45 +00:00
t . Run ( "UpdateUser" , func ( t * testing . T ) {
2022-04-12 14:05:21 +00:00
t . Parallel ( )
2022-09-09 02:16:16 +00:00
auditor := audit . NewMock ( )
client := coderdtest . New ( t , & coderdtest . Options { Auditor : auditor } )
2023-03-30 17:13:03 +00:00
numLogs := len ( auditor . AuditLogs ( ) )
2023-02-06 20:12:50 +00:00
2022-04-12 14:05:21 +00:00
coderdtest . CreateFirstUser ( t , client )
2023-02-06 20:12:50 +00:00
numLogs ++ // add an audit log for login
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
_ , _ = client . User ( ctx , codersdk . Me )
userProfile , err := client . UpdateUserProfile ( ctx , codersdk . Me , codersdk . UpdateUserProfileRequest {
2022-04-12 14:05:21 +00:00
Username : "newusername" ,
2024-01-17 12:20:45 +00:00
Name : "Mr User" ,
2022-04-12 14:05:21 +00:00
} )
require . NoError ( t , err )
require . Equal ( t , userProfile . Username , "newusername" )
2024-01-17 12:20:45 +00:00
require . Equal ( t , userProfile . Name , "Mr User" )
2023-02-06 20:12:50 +00:00
numLogs ++ // add an audit log for user update
2023-03-30 17:13:03 +00:00
require . Len ( t , auditor . AuditLogs ( ) , numLogs )
require . Equal ( t , database . AuditActionWrite , auditor . AuditLogs ( ) [ numLogs - 1 ] . Action )
2022-04-12 14:05:21 +00:00
} )
2024-01-17 12:20:45 +00:00
t . Run ( "InvalidRealUserName" , func ( t * testing . T ) {
t . Parallel ( )
client := coderdtest . New ( t , nil )
user := coderdtest . CreateFirstUser ( t , client )
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
_ , err := client . CreateUser ( ctx , codersdk . CreateUserRequest {
Email : "john@coder.com" ,
Username : "john" ,
Password : "SomeSecurePassword!" ,
OrganizationID : user . OrganizationID ,
} )
require . NoError ( t , err )
_ , err = client . UpdateUserProfile ( ctx , codersdk . Me , codersdk . UpdateUserProfileRequest {
Name : " Mr Bean" , // must not have leading space
} )
var apiErr * codersdk . Error
require . ErrorAs ( t , err , & apiErr )
require . Equal ( t , http . StatusBadRequest , apiErr . StatusCode ( ) )
} )
2022-04-12 14:05:21 +00:00
}
2022-05-06 14:20:08 +00:00
func TestUpdateUserPassword ( t * testing . T ) {
t . Parallel ( )
t . Run ( "MemberCantUpdateAdminPassword" , func ( t * testing . T ) {
t . Parallel ( )
client := coderdtest . New ( t , nil )
2023-10-10 10:14:20 +00:00
owner := coderdtest . CreateFirstUser ( t , client )
member , _ := coderdtest . CreateAnotherUser ( t , client , owner . OrganizationID )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
2023-10-10 10:14:20 +00:00
err := member . UpdateUserPassword ( ctx , owner . UserID . String ( ) , codersdk . UpdateUserPasswordRequest {
2022-05-06 14:20:08 +00:00
Password : "newpassword" ,
} )
require . Error ( t , err , "member should not be able to update admin password" )
} )
t . Run ( "AdminCanUpdateMemberPassword" , func ( t * testing . T ) {
t . Parallel ( )
client := coderdtest . New ( t , nil )
2023-10-10 10:14:20 +00:00
owner := coderdtest . CreateFirstUser ( t , client )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
member , err := client . CreateUser ( ctx , codersdk . CreateUserRequest {
2022-05-06 14:20:08 +00:00
Email : "coder@coder.com" ,
Username : "coder" ,
2023-02-08 20:10:08 +00:00
Password : "SomeStrongPassword!" ,
2023-10-10 10:14:20 +00:00
OrganizationID : owner . OrganizationID ,
2022-05-06 14:20:08 +00:00
} )
require . NoError ( t , err , "create member" )
2022-08-09 17:17:00 +00:00
err = client . UpdateUserPassword ( ctx , member . ID . String ( ) , codersdk . UpdateUserPasswordRequest {
2023-02-08 20:10:08 +00:00
Password : "SomeNewStrongPassword!" ,
2022-05-06 14:20:08 +00:00
} )
require . NoError ( t , err , "admin should be able to update member password" )
// Check if the member can login using the new password
2022-08-09 17:17:00 +00:00
_ , err = client . LoginWithPassword ( ctx , codersdk . LoginWithPasswordRequest {
2022-05-06 14:20:08 +00:00
Email : "coder@coder.com" ,
2023-02-08 20:10:08 +00:00
Password : "SomeNewStrongPassword!" ,
2022-05-06 14:20:08 +00:00
} )
require . NoError ( t , err , "member should login successfully with the new password" )
} )
2022-05-27 17:29:55 +00:00
t . Run ( "MemberCanUpdateOwnPassword" , func ( t * testing . T ) {
t . Parallel ( )
2022-09-09 02:16:16 +00:00
auditor := audit . NewMock ( )
client := coderdtest . New ( t , & coderdtest . Options { Auditor : auditor } )
2023-03-30 17:13:03 +00:00
numLogs := len ( auditor . AuditLogs ( ) )
2023-02-06 20:12:50 +00:00
2023-10-10 10:14:20 +00:00
owner := coderdtest . CreateFirstUser ( t , client )
2023-02-06 20:12:50 +00:00
numLogs ++ // add an audit log for user create
numLogs ++ // add an audit log for login
2023-10-10 10:14:20 +00:00
member , _ := coderdtest . CreateAnotherUser ( t , client , owner . OrganizationID )
2023-02-06 20:12:50 +00:00
numLogs ++ // add an audit log for user create
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
err := member . UpdateUserPassword ( ctx , "me" , codersdk . UpdateUserPasswordRequest {
2023-02-08 20:10:08 +00:00
OldPassword : "SomeSecurePassword!" ,
Password : "MyNewSecurePassword!" ,
2022-05-27 17:29:55 +00:00
} )
2023-02-06 20:12:50 +00:00
numLogs ++ // add an audit log for user update
2022-05-27 17:29:55 +00:00
require . NoError ( t , err , "member should be able to update own password" )
2023-02-06 20:12:50 +00:00
2023-03-30 17:13:03 +00:00
require . Len ( t , auditor . AuditLogs ( ) , numLogs )
require . Equal ( t , database . AuditActionWrite , auditor . AuditLogs ( ) [ numLogs - 1 ] . Action )
2022-05-27 17:29:55 +00:00
} )
t . Run ( "MemberCantUpdateOwnPasswordWithoutOldPassword" , func ( t * testing . T ) {
t . Parallel ( )
client := coderdtest . New ( t , nil )
2023-10-10 10:14:20 +00:00
owner := coderdtest . CreateFirstUser ( t , client )
member , _ := coderdtest . CreateAnotherUser ( t , client , owner . OrganizationID )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
err := member . UpdateUserPassword ( ctx , "me" , codersdk . UpdateUserPasswordRequest {
2022-05-27 17:29:55 +00:00
Password : "newpassword" ,
} )
require . Error ( t , err , "member should not be able to update own password without providing old password" )
} )
2022-06-10 01:43:54 +00:00
t . Run ( "AdminCanUpdateOwnPasswordWithoutOldPassword" , func ( t * testing . T ) {
2022-05-27 17:29:55 +00:00
t . Parallel ( )
2022-09-09 02:16:16 +00:00
auditor := audit . NewMock ( )
client := coderdtest . New ( t , & coderdtest . Options { Auditor : auditor } )
2023-03-30 17:13:03 +00:00
numLogs := len ( auditor . AuditLogs ( ) )
2023-02-06 20:12:50 +00:00
2022-05-27 17:29:55 +00:00
_ = coderdtest . CreateFirstUser ( t , client )
2023-02-06 20:12:50 +00:00
numLogs ++ // add an audit log for login
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
err := client . UpdateUserPassword ( ctx , "me" , codersdk . UpdateUserPasswordRequest {
2023-02-08 20:10:08 +00:00
Password : "MySecurePassword!" ,
2022-05-27 17:29:55 +00:00
} )
2023-02-06 20:12:50 +00:00
numLogs ++ // add an audit log for user update
2022-06-10 01:43:54 +00:00
require . NoError ( t , err , "admin should be able to update own password without providing old password" )
2023-02-06 20:12:50 +00:00
2023-03-30 17:13:03 +00:00
require . Len ( t , auditor . AuditLogs ( ) , numLogs )
require . Equal ( t , database . AuditActionWrite , auditor . AuditLogs ( ) [ numLogs - 1 ] . Action )
2022-05-27 17:29:55 +00:00
} )
2022-10-20 02:12:03 +00:00
t . Run ( "ChangingPasswordDeletesKeys" , func ( t * testing . T ) {
t . Parallel ( )
client := coderdtest . New ( t , nil )
user := coderdtest . CreateFirstUser ( t , client )
2023-03-23 22:42:20 +00:00
ctx := testutil . Context ( t , testutil . WaitLong )
2022-10-20 02:12:03 +00:00
apikey1 , err := client . CreateToken ( ctx , user . UserID . String ( ) , codersdk . CreateTokenRequest { } )
require . NoError ( t , err )
apikey2 , err := client . CreateToken ( ctx , user . UserID . String ( ) , codersdk . CreateTokenRequest { } )
require . NoError ( t , err )
err = client . UpdateUserPassword ( ctx , "me" , codersdk . UpdateUserPasswordRequest {
2023-02-08 20:10:08 +00:00
Password : "MyNewSecurePassword!" ,
2022-10-20 02:12:03 +00:00
} )
require . NoError ( t , err )
// Trying to get an API key should fail since our client's token
// has been deleted.
2023-03-02 17:39:38 +00:00
_ , err = client . APIKeyByID ( ctx , user . UserID . String ( ) , apikey1 . Key )
2022-10-20 02:12:03 +00:00
require . Error ( t , err )
cerr := coderdtest . SDKError ( t , err )
require . Equal ( t , http . StatusUnauthorized , cerr . StatusCode ( ) )
resp , err := client . LoginWithPassword ( ctx , codersdk . LoginWithPasswordRequest {
Email : coderdtest . FirstUserParams . Email ,
2023-02-08 20:10:08 +00:00
Password : "MyNewSecurePassword!" ,
2022-10-20 02:12:03 +00:00
} )
require . NoError ( t , err )
2022-11-09 13:31:24 +00:00
client . SetSessionToken ( resp . SessionToken )
2022-10-20 02:12:03 +00:00
// Trying to get an API key should fail since all keys are deleted
// on password change.
2023-03-02 17:39:38 +00:00
_ , err = client . APIKeyByID ( ctx , user . UserID . String ( ) , apikey1 . Key )
2022-10-20 02:12:03 +00:00
require . Error ( t , err )
cerr = coderdtest . SDKError ( t , err )
require . Equal ( t , http . StatusNotFound , cerr . StatusCode ( ) )
2023-03-02 17:39:38 +00:00
_ , err = client . APIKeyByID ( ctx , user . UserID . String ( ) , apikey2 . Key )
2022-10-20 02:12:03 +00:00
require . Error ( t , err )
cerr = coderdtest . SDKError ( t , err )
require . Equal ( t , http . StatusNotFound , cerr . StatusCode ( ) )
} )
t . Run ( "PasswordsMustDiffer" , func ( t * testing . T ) {
t . Parallel ( )
client := coderdtest . New ( t , nil )
_ = coderdtest . CreateFirstUser ( t , client )
2023-03-23 22:42:20 +00:00
ctx := testutil . Context ( t , testutil . WaitLong )
2022-10-20 02:12:03 +00:00
err := client . UpdateUserPassword ( ctx , "me" , codersdk . UpdateUserPasswordRequest {
Password : coderdtest . FirstUserParams . Password ,
} )
require . Error ( t , err )
cerr := coderdtest . SDKError ( t , err )
require . Equal ( t , http . StatusBadRequest , cerr . StatusCode ( ) )
} )
2022-05-06 14:20:08 +00:00
}
2022-08-09 18:16:53 +00:00
func TestGrantSiteRoles ( t * testing . T ) {
2022-04-29 14:04:19 +00:00
t . Parallel ( )
2022-05-25 16:00:59 +00:00
requireStatusCode := func ( t * testing . T , err error , statusCode int ) {
t . Helper ( )
var e * codersdk . Error
require . ErrorAs ( t , err , & e , "error is codersdk error" )
require . Equal ( t , statusCode , e . StatusCode ( ) , "correct status code" )
}
2022-08-09 18:16:53 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
2022-08-21 22:32:53 +00:00
t . Cleanup ( cancel )
2022-08-09 18:16:53 +00:00
var err error
2022-04-29 14:04:19 +00:00
2022-08-09 18:16:53 +00:00
admin := coderdtest . New ( t , nil )
first := coderdtest . CreateFirstUser ( t , admin )
2023-02-06 23:48:21 +00:00
member , _ := coderdtest . CreateAnotherUser ( t , admin , first . OrganizationID )
orgAdmin , _ := coderdtest . CreateAnotherUser ( t , admin , first . OrganizationID , rbac . RoleOrgAdmin ( first . OrganizationID ) )
2022-08-09 18:16:53 +00:00
randOrg , err := admin . CreateOrganization ( ctx , codersdk . CreateOrganizationRequest {
Name : "random" ,
2022-04-29 14:04:19 +00:00
} )
2022-08-09 18:16:53 +00:00
require . NoError ( t , err )
2023-02-06 23:48:21 +00:00
_ , randOrgUser := coderdtest . CreateAnotherUser ( t , admin , randOrg . ID , rbac . RoleOrgAdmin ( randOrg . ID ) )
userAdmin , _ := coderdtest . CreateAnotherUser ( t , admin , first . OrganizationID , rbac . RoleUserAdmin ( ) )
2022-08-09 18:16:53 +00:00
const newUser = "newUser"
testCases := [ ] struct {
2022-08-24 19:58:57 +00:00
Name string
Client * codersdk . Client
OrgID uuid . UUID
AssignToUser string
Roles [ ] string
ExpectedRoles [ ] string
Error bool
StatusCode int
2022-08-09 18:16:53 +00:00
} {
{
Name : "OrgRoleInSite" ,
Client : admin ,
AssignToUser : codersdk . Me ,
Roles : [ ] string { rbac . RoleOrgAdmin ( first . OrganizationID ) } ,
Error : true ,
StatusCode : http . StatusBadRequest ,
} ,
{
Name : "UserNotExists" ,
Client : admin ,
AssignToUser : uuid . NewString ( ) ,
2022-08-15 19:40:19 +00:00
Roles : [ ] string { rbac . RoleOwner ( ) } ,
2022-08-09 18:16:53 +00:00
Error : true ,
StatusCode : http . StatusBadRequest ,
} ,
{
Name : "MemberCannotUpdateRoles" ,
Client : member ,
AssignToUser : first . UserID . String ( ) ,
Roles : [ ] string { } ,
Error : true ,
2023-10-11 05:41:14 +00:00
StatusCode : http . StatusBadRequest ,
2022-08-09 18:16:53 +00:00
} ,
{
// Cannot update your own roles
Name : "AdminOnSelf" ,
Client : admin ,
AssignToUser : first . UserID . String ( ) ,
Roles : [ ] string { } ,
Error : true ,
StatusCode : http . StatusBadRequest ,
} ,
{
Name : "SiteRoleInOrg" ,
Client : admin ,
OrgID : first . OrganizationID ,
AssignToUser : codersdk . Me ,
2022-08-15 19:40:19 +00:00
Roles : [ ] string { rbac . RoleOwner ( ) } ,
2022-08-09 18:16:53 +00:00
Error : true ,
StatusCode : http . StatusBadRequest ,
} ,
{
Name : "RoleInNotMemberOrg" ,
Client : orgAdmin ,
OrgID : randOrg . ID ,
AssignToUser : randOrgUser . ID . String ( ) ,
Roles : [ ] string { rbac . RoleOrgMember ( randOrg . ID ) } ,
Error : true ,
2023-02-14 14:27:06 +00:00
StatusCode : http . StatusNotFound ,
2022-08-09 18:16:53 +00:00
} ,
{
Name : "AdminUpdateOrgSelf" ,
Client : admin ,
OrgID : first . OrganizationID ,
AssignToUser : first . UserID . String ( ) ,
Roles : [ ] string { } ,
Error : true ,
StatusCode : http . StatusBadRequest ,
} ,
{
Name : "OrgAdminPromote" ,
Client : orgAdmin ,
OrgID : first . OrganizationID ,
AssignToUser : newUser ,
Roles : [ ] string { rbac . RoleOrgAdmin ( first . OrganizationID ) } ,
2022-08-24 19:58:57 +00:00
ExpectedRoles : [ ] string {
rbac . RoleOrgAdmin ( first . OrganizationID ) ,
} ,
Error : false ,
} ,
{
Name : "UserAdminMakeMember" ,
Client : userAdmin ,
AssignToUser : newUser ,
Roles : [ ] string { rbac . RoleMember ( ) } ,
ExpectedRoles : [ ] string {
rbac . RoleMember ( ) ,
} ,
Error : false ,
2022-08-09 18:16:53 +00:00
} ,
}
2022-04-29 14:04:19 +00:00
2022-08-09 18:16:53 +00:00
for _ , c := range testCases {
c := c
t . Run ( c . Name , func ( t * testing . T ) {
t . Parallel ( )
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
var err error
if c . AssignToUser == newUser {
orgID := first . OrganizationID
if c . OrgID != uuid . Nil {
orgID = c . OrgID
}
2023-02-06 23:48:21 +00:00
_ , newUser := coderdtest . CreateAnotherUser ( t , admin , orgID )
2022-08-09 18:16:53 +00:00
c . AssignToUser = newUser . ID . String ( )
}
2022-08-24 19:58:57 +00:00
var newRoles [ ] codersdk . Role
2022-08-09 18:16:53 +00:00
if c . OrgID != uuid . Nil {
// Org assign
2022-08-24 19:58:57 +00:00
var mem codersdk . OrganizationMember
mem , err = c . Client . UpdateOrganizationMemberRoles ( ctx , c . OrgID , c . AssignToUser , codersdk . UpdateRoles {
2022-08-09 18:16:53 +00:00
Roles : c . Roles ,
} )
2022-08-24 19:58:57 +00:00
newRoles = mem . Roles
2022-08-09 18:16:53 +00:00
} else {
// Site assign
2022-08-24 19:58:57 +00:00
var user codersdk . User
user , err = c . Client . UpdateUserRoles ( ctx , c . AssignToUser , codersdk . UpdateRoles {
2022-08-09 18:16:53 +00:00
Roles : c . Roles ,
} )
2022-08-24 19:58:57 +00:00
newRoles = user . Roles
2022-08-09 18:16:53 +00:00
}
if c . Error {
require . Error ( t , err )
requireStatusCode ( t , err , c . StatusCode )
} else {
require . NoError ( t , err )
2022-08-24 19:58:57 +00:00
roles := make ( [ ] string , 0 , len ( newRoles ) )
for _ , r := range newRoles {
roles = append ( roles , r . Name )
}
require . ElementsMatch ( t , roles , c . ExpectedRoles )
2022-08-09 18:16:53 +00:00
}
2022-04-29 14:04:19 +00:00
} )
2022-08-09 18:16:53 +00:00
}
}
2022-04-29 14:04:19 +00:00
2022-08-09 18:16:53 +00:00
// TestInitialRoles ensures the starting roles for the first user are correct.
func TestInitialRoles ( t * testing . T ) {
t . Parallel ( )
ctx := context . Background ( )
client := coderdtest . New ( t , nil )
first := coderdtest . CreateFirstUser ( t , client )
2023-01-29 21:47:24 +00:00
roles , err := client . UserRoles ( ctx , codersdk . Me )
2022-08-09 18:16:53 +00:00
require . NoError ( t , err )
require . ElementsMatch ( t , roles . Roles , [ ] string {
2022-08-15 19:40:19 +00:00
rbac . RoleOwner ( ) ,
2022-08-09 18:16:53 +00:00
} , "should be a member and admin" )
2024-03-05 20:06:35 +00:00
require . ElementsMatch ( t , roles . OrganizationRoles [ first . OrganizationID ] , [ ] string { } , "should be a member" )
2022-04-29 14:04:19 +00:00
}
2022-04-26 14:00:07 +00:00
func TestPutUserSuspend ( t * testing . T ) {
t . Parallel ( )
2022-08-31 15:26:36 +00:00
t . Run ( "SuspendAnOwner" , func ( t * testing . T ) {
t . Parallel ( )
client := coderdtest . New ( t , nil )
me := coderdtest . CreateFirstUser ( t , client )
2023-02-06 23:48:21 +00:00
_ , user := coderdtest . CreateAnotherUser ( t , client , me . OrganizationID , rbac . RoleOwner ( ) )
2022-08-31 15:26:36 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
_ , err := client . UpdateUserStatus ( ctx , user . Username , codersdk . UserStatusSuspended )
require . Error ( t , err , "cannot suspend owners" )
} )
2022-04-26 14:00:07 +00:00
t . Run ( "SuspendAnotherUser" , func ( t * testing . T ) {
t . Parallel ( )
2022-09-09 02:16:16 +00:00
auditor := audit . NewMock ( )
client := coderdtest . New ( t , & coderdtest . Options { Auditor : auditor } )
2023-03-30 17:13:03 +00:00
numLogs := len ( auditor . AuditLogs ( ) )
2023-02-06 20:12:50 +00:00
2022-04-26 14:00:07 +00:00
me := coderdtest . CreateFirstUser ( t , client )
2023-02-06 20:12:50 +00:00
numLogs ++ // add an audit log for user create
numLogs ++ // add an audit log for login
2023-02-06 23:48:21 +00:00
_ , user := coderdtest . CreateAnotherUser ( t , client , me . OrganizationID )
2023-02-06 20:12:50 +00:00
numLogs ++ // add an audit log for user create
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
user , err := client . UpdateUserStatus ( ctx , user . Username , codersdk . UserStatusSuspended )
2022-04-26 14:00:07 +00:00
require . NoError ( t , err )
require . Equal ( t , user . Status , codersdk . UserStatusSuspended )
2023-02-06 20:12:50 +00:00
numLogs ++ // add an audit log for user update
2023-03-30 17:13:03 +00:00
require . Len ( t , auditor . AuditLogs ( ) , numLogs )
require . Equal ( t , database . AuditActionWrite , auditor . AuditLogs ( ) [ numLogs - 1 ] . Action )
2022-04-26 14:00:07 +00:00
} )
t . Run ( "SuspendItSelf" , func ( t * testing . T ) {
t . Parallel ( )
client := coderdtest . New ( t , nil )
coderdtest . CreateFirstUser ( t , client )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
client . User ( ctx , codersdk . Me )
_ , err := client . UpdateUserStatus ( ctx , codersdk . Me , codersdk . UserStatusSuspended )
2022-04-26 14:00:07 +00:00
2022-05-16 20:29:27 +00:00
require . ErrorContains ( t , err , "suspend yourself" , "cannot suspend yourself" )
2022-04-26 14:00:07 +00:00
} )
}
2023-08-02 14:31:25 +00:00
func TestActivateDormantUser ( t * testing . T ) {
t . Parallel ( )
client := coderdtest . New ( t , nil )
// Create users
me := coderdtest . CreateFirstUser ( t , client )
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
anotherUser , err := client . CreateUser ( ctx , codersdk . CreateUserRequest {
Email : "coder@coder.com" ,
Username : "coder" ,
Password : "SomeStrongPassword!" ,
OrganizationID : me . OrganizationID ,
} )
require . NoError ( t , err )
// Ensure that new user has dormant account
require . Equal ( t , codersdk . UserStatusDormant , anotherUser . Status )
// Activate user account
_ , err = client . UpdateUserStatus ( ctx , anotherUser . Username , codersdk . UserStatusActive )
require . NoError ( t , err )
// Verify if the account is active now
anotherUser , err = client . User ( ctx , anotherUser . Username )
require . NoError ( t , err )
require . Equal ( t , codersdk . UserStatusActive , anotherUser . Status )
}
2022-04-29 16:44:22 +00:00
func TestGetUser ( t * testing . T ) {
2022-02-06 00:24:51 +00:00
t . Parallel ( )
2022-04-28 14:10:17 +00:00
2022-04-29 16:44:22 +00:00
t . Run ( "ByMe" , func ( t * testing . T ) {
t . Parallel ( )
client := coderdtest . New ( t , nil )
firstUser := coderdtest . CreateFirstUser ( t , client )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
user , err := client . User ( ctx , codersdk . Me )
2022-04-29 16:44:22 +00:00
require . NoError ( t , err )
require . Equal ( t , firstUser . UserID , user . ID )
require . Equal ( t , firstUser . OrganizationID , user . OrganizationIDs [ 0 ] )
} )
t . Run ( "ByID" , func ( t * testing . T ) {
t . Parallel ( )
client := coderdtest . New ( t , nil )
firstUser := coderdtest . CreateFirstUser ( t , client )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
user , err := client . User ( ctx , firstUser . UserID . String ( ) )
2022-04-29 16:44:22 +00:00
require . NoError ( t , err )
require . Equal ( t , firstUser . UserID , user . ID )
require . Equal ( t , firstUser . OrganizationID , user . OrganizationIDs [ 0 ] )
} )
t . Run ( "ByUsername" , func ( t * testing . T ) {
t . Parallel ( )
client := coderdtest . New ( t , nil )
firstUser := coderdtest . CreateFirstUser ( t , client )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
exp , err := client . User ( ctx , firstUser . UserID . String ( ) )
2022-04-29 16:44:22 +00:00
require . NoError ( t , err )
2022-08-09 17:17:00 +00:00
user , err := client . User ( ctx , exp . Username )
2022-04-29 16:44:22 +00:00
require . NoError ( t , err )
require . Equal ( t , exp , user )
} )
2022-02-06 00:24:51 +00:00
}
2022-06-24 15:02:23 +00:00
// TestUsersFilter creates a set of users to run various filters against for testing.
func TestUsersFilter ( t * testing . T ) {
t . Parallel ( )
2023-06-22 20:24:48 +00:00
client , _ , api := coderdtest . NewWithAPI ( t , & coderdtest . Options { IncludeProvisionerDaemon : true } )
2022-06-24 15:02:23 +00:00
first := coderdtest . CreateFirstUser ( t , client )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
2022-08-21 22:32:53 +00:00
t . Cleanup ( cancel )
2022-08-09 17:17:00 +00:00
firstUser , err := client . User ( ctx , codersdk . Me )
2022-06-24 15:02:23 +00:00
require . NoError ( t , err , "fetch me" )
2023-06-22 20:24:48 +00:00
// Noon on Jan 18 is the "now" for this test for last_seen timestamps.
// All these values are equal
// 2023-01-18T12:00:00Z (UTC)
// 2023-01-18T07:00:00-05:00 (America/New_York)
// 2023-01-18T13:00:00+01:00 (Europe/Madrid)
// 2023-01-16T00:00:00+12:00 (Asia/Anadyr)
lastSeenNow := time . Date ( 2023 , 1 , 18 , 12 , 0 , 0 , 0 , time . UTC )
2022-06-24 15:02:23 +00:00
users := make ( [ ] codersdk . User , 0 )
users = append ( users , firstUser )
for i := 0 ; i < 15 ; i ++ {
roles := [ ] string { }
if i % 2 == 0 {
2022-08-31 15:26:36 +00:00
roles = append ( roles , rbac . RoleTemplateAdmin ( ) , rbac . RoleUserAdmin ( ) )
2022-06-24 15:02:23 +00:00
}
if i % 3 == 0 {
roles = append ( roles , "auditor" )
}
2023-06-22 20:24:48 +00:00
userClient , userData := coderdtest . CreateAnotherUser ( t , client , first . OrganizationID , roles ... )
// Set the last seen for each user to a unique day
// nolint:gocritic // Unit test
_ , err := api . Database . UpdateUserLastSeenAt ( dbauthz . AsSystemRestricted ( ctx ) , database . UpdateUserLastSeenAtParams {
ID : userData . ID ,
LastSeenAt : lastSeenNow . Add ( - 1 * time . Hour * 24 * time . Duration ( i ) ) ,
UpdatedAt : time . Now ( ) ,
} )
require . NoError ( t , err , "set a last seen" )
2022-08-09 17:17:00 +00:00
user , err := userClient . User ( ctx , codersdk . Me )
2022-06-24 15:02:23 +00:00
require . NoError ( t , err , "fetch me" )
if i % 4 == 0 {
2022-08-09 17:17:00 +00:00
user , err = client . UpdateUserStatus ( ctx , user . ID . String ( ) , codersdk . UserStatusSuspended )
2022-06-24 15:02:23 +00:00
require . NoError ( t , err , "suspend user" )
}
2022-06-24 23:55:28 +00:00
if i % 5 == 0 {
2022-08-09 17:17:00 +00:00
user , err = client . UpdateUserProfile ( ctx , user . ID . String ( ) , codersdk . UpdateUserProfileRequest {
2022-06-24 23:55:28 +00:00
Username : strings . ToUpper ( user . Username ) ,
} )
require . NoError ( t , err , "update username to uppercase" )
}
2022-06-24 15:02:23 +00:00
users = append ( users , user )
}
// --- Setup done ---
testCases := [ ] struct {
Name string
Filter codersdk . UsersRequest
// If FilterF is true, we include it in the expected results
FilterF func ( f codersdk . UsersRequest , user codersdk . User ) bool
} {
{
Name : "All" ,
Filter : codersdk . UsersRequest {
Status : codersdk . UserStatusSuspended + "," + codersdk . UserStatusActive ,
} ,
FilterF : func ( _ codersdk . UsersRequest , u codersdk . User ) bool {
return true
} ,
} ,
{
Name : "Active" ,
Filter : codersdk . UsersRequest {
Status : codersdk . UserStatusActive ,
} ,
FilterF : func ( _ codersdk . UsersRequest , u codersdk . User ) bool {
return u . Status == codersdk . UserStatusActive
} ,
} ,
2022-06-24 23:55:28 +00:00
{
Name : "ActiveUppercase" ,
Filter : codersdk . UsersRequest {
Status : "ACTIVE" ,
} ,
FilterF : func ( _ codersdk . UsersRequest , u codersdk . User ) bool {
return u . Status == codersdk . UserStatusActive
} ,
} ,
2022-06-24 15:02:23 +00:00
{
Name : "Suspended" ,
Filter : codersdk . UsersRequest {
Status : codersdk . UserStatusSuspended ,
} ,
FilterF : func ( _ codersdk . UsersRequest , u codersdk . User ) bool {
return u . Status == codersdk . UserStatusSuspended
} ,
} ,
{
Name : "NameContains" ,
Filter : codersdk . UsersRequest {
Search : "a" ,
} ,
FilterF : func ( _ codersdk . UsersRequest , u codersdk . User ) bool {
2022-06-24 23:55:28 +00:00
return ( strings . ContainsAny ( u . Username , "aA" ) || strings . ContainsAny ( u . Email , "aA" ) )
2022-06-24 15:02:23 +00:00
} ,
} ,
{
Name : "Admins" ,
Filter : codersdk . UsersRequest {
2022-08-15 19:40:19 +00:00
Role : rbac . RoleOwner ( ) ,
2022-06-24 15:02:23 +00:00
Status : codersdk . UserStatusSuspended + "," + codersdk . UserStatusActive ,
} ,
FilterF : func ( _ codersdk . UsersRequest , u codersdk . User ) bool {
for _ , r := range u . Roles {
2022-08-15 19:40:19 +00:00
if r . Name == rbac . RoleOwner ( ) {
2022-06-24 15:02:23 +00:00
return true
}
}
return false
} ,
} ,
2022-06-24 23:55:28 +00:00
{
Name : "AdminsUppercase" ,
Filter : codersdk . UsersRequest {
2022-08-15 19:40:19 +00:00
Role : "OWNER" ,
2022-06-24 23:55:28 +00:00
Status : codersdk . UserStatusSuspended + "," + codersdk . UserStatusActive ,
} ,
FilterF : func ( _ codersdk . UsersRequest , u codersdk . User ) bool {
for _ , r := range u . Roles {
2022-08-15 19:40:19 +00:00
if r . Name == rbac . RoleOwner ( ) {
2022-06-24 23:55:28 +00:00
return true
}
}
return false
} ,
} ,
{
Name : "Members" ,
Filter : codersdk . UsersRequest {
Role : rbac . RoleMember ( ) ,
Status : codersdk . UserStatusSuspended + "," + codersdk . UserStatusActive ,
} ,
FilterF : func ( _ codersdk . UsersRequest , u codersdk . User ) bool {
return true
} ,
} ,
2022-06-24 15:02:23 +00:00
{
Name : "SearchQuery" ,
Filter : codersdk . UsersRequest {
2022-08-15 19:40:19 +00:00
SearchQuery : "i role:owner status:active" ,
2022-06-24 15:02:23 +00:00
} ,
FilterF : func ( _ codersdk . UsersRequest , u codersdk . User ) bool {
for _ , r := range u . Roles {
2022-08-15 19:40:19 +00:00
if r . Name == rbac . RoleOwner ( ) {
2022-06-24 23:55:28 +00:00
return ( strings . ContainsAny ( u . Username , "iI" ) || strings . ContainsAny ( u . Email , "iI" ) ) &&
u . Status == codersdk . UserStatusActive
}
}
return false
} ,
} ,
{
Name : "SearchQueryInsensitive" ,
Filter : codersdk . UsersRequest {
2022-08-15 19:40:19 +00:00
SearchQuery : "i Role:Owner STATUS:Active" ,
2022-06-24 23:55:28 +00:00
} ,
FilterF : func ( _ codersdk . UsersRequest , u codersdk . User ) bool {
for _ , r := range u . Roles {
2022-08-15 19:40:19 +00:00
if r . Name == rbac . RoleOwner ( ) {
2022-06-24 23:55:28 +00:00
return ( strings . ContainsAny ( u . Username , "iI" ) || strings . ContainsAny ( u . Email , "iI" ) ) &&
2022-06-24 15:02:23 +00:00
u . Status == codersdk . UserStatusActive
}
}
return false
} ,
} ,
2023-06-22 20:24:48 +00:00
{
Name : "LastSeenBeforeNow" ,
Filter : codersdk . UsersRequest {
SearchQuery : ` last_seen_before:"2023-01-16T00:00:00+12:00" ` ,
} ,
FilterF : func ( _ codersdk . UsersRequest , u codersdk . User ) bool {
return u . LastSeenAt . Before ( lastSeenNow )
} ,
} ,
{
Name : "LastSeenLastWeek" ,
Filter : codersdk . UsersRequest {
SearchQuery : ` last_seen_before:"2023-01-14T23:59:59Z" last_seen_after:"2023-01-08T00:00:00Z" ` ,
} ,
FilterF : func ( _ codersdk . UsersRequest , u codersdk . User ) bool {
start := time . Date ( 2023 , 1 , 8 , 0 , 0 , 0 , 0 , time . UTC )
end := time . Date ( 2023 , 1 , 14 , 23 , 59 , 59 , 0 , time . UTC )
return u . LastSeenAt . Before ( end ) && u . LastSeenAt . After ( start )
} ,
} ,
2022-06-24 15:02:23 +00:00
}
for _ , c := range testCases {
c := c
t . Run ( c . Name , func ( t * testing . T ) {
t . Parallel ( )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
matched , err := client . Users ( ctx , c . Filter )
2022-06-24 15:02:23 +00:00
require . NoError ( t , err , "fetch workspaces" )
exp := make ( [ ] codersdk . User , 0 )
for _ , made := range users {
match := c . FilterF ( c . Filter , made )
if match {
exp = append ( exp , made )
}
}
2022-11-14 22:22:57 +00:00
require . ElementsMatch ( t , exp , matched . Users , "expected workspaces returned" )
2022-06-24 15:02:23 +00:00
} )
}
}
2022-04-18 17:19:47 +00:00
func TestGetUsers ( t * testing . T ) {
t . Parallel ( )
2022-04-29 13:29:53 +00:00
t . Run ( "AllUsers" , func ( t * testing . T ) {
t . Parallel ( )
client := coderdtest . New ( t , nil )
user := coderdtest . CreateFirstUser ( t , client )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
client . CreateUser ( ctx , codersdk . CreateUserRequest {
2022-04-29 13:29:53 +00:00
Email : "alice@email.com" ,
Username : "alice" ,
2023-02-08 20:10:08 +00:00
Password : "MySecurePassword!" ,
2022-04-29 13:29:53 +00:00
OrganizationID : user . OrganizationID ,
} )
// No params is all users
2022-11-14 22:22:57 +00:00
res , err := client . Users ( ctx , codersdk . UsersRequest { } )
2022-04-29 13:29:53 +00:00
require . NoError ( t , err )
2022-11-14 22:22:57 +00:00
require . Len ( t , res . Users , 2 )
require . Len ( t , res . Users [ 0 ] . OrganizationIDs , 1 )
2022-04-29 13:29:53 +00:00
} )
t . Run ( "ActiveUsers" , func ( t * testing . T ) {
t . Parallel ( )
2022-05-16 20:29:27 +00:00
active := make ( [ ] codersdk . User , 0 )
2022-04-29 13:29:53 +00:00
client := coderdtest . New ( t , nil )
first := coderdtest . CreateFirstUser ( t , client )
2022-05-16 20:29:27 +00:00
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
firstUser , err := client . User ( ctx , first . UserID . String ( ) )
2022-05-16 20:29:27 +00:00
require . NoError ( t , err , "" )
active = append ( active , firstUser )
// Alice will be suspended
2022-08-09 17:17:00 +00:00
alice , err := client . CreateUser ( ctx , codersdk . CreateUserRequest {
2022-04-29 13:29:53 +00:00
Email : "alice@email.com" ,
Username : "alice" ,
2023-02-08 20:10:08 +00:00
Password : "MySecurePassword!" ,
2022-04-29 13:29:53 +00:00
OrganizationID : first . OrganizationID ,
} )
require . NoError ( t , err )
2023-08-02 14:31:25 +00:00
_ , err = client . UpdateUserStatus ( ctx , alice . Username , codersdk . UserStatusSuspended )
require . NoError ( t , err )
// Tom will be active
tom , err := client . CreateUser ( ctx , codersdk . CreateUserRequest {
Email : "tom@email.com" ,
Username : "tom" ,
2023-02-08 20:10:08 +00:00
Password : "MySecurePassword!" ,
2022-04-29 13:29:53 +00:00
OrganizationID : first . OrganizationID ,
} )
require . NoError ( t , err )
2023-08-02 14:31:25 +00:00
tom , err = client . UpdateUserStatus ( ctx , tom . Username , codersdk . UserStatusActive )
2022-04-29 13:29:53 +00:00
require . NoError ( t , err )
2023-08-02 14:31:25 +00:00
active = append ( active , tom )
2022-04-29 13:29:53 +00:00
2022-11-14 22:22:57 +00:00
res , err := client . Users ( ctx , codersdk . UsersRequest {
2022-06-24 15:02:23 +00:00
Status : codersdk . UserStatusActive ,
2022-04-29 13:29:53 +00:00
} )
require . NoError ( t , err )
2022-11-14 22:22:57 +00:00
require . ElementsMatch ( t , active , res . Users )
2022-04-18 17:19:47 +00:00
} )
}
2022-11-14 22:22:57 +00:00
func TestGetUsersPagination ( t * testing . T ) {
2022-11-08 15:58:44 +00:00
t . Parallel ( )
2022-11-14 22:22:57 +00:00
client := coderdtest . New ( t , nil )
first := coderdtest . CreateFirstUser ( t , client )
2022-11-08 15:58:44 +00:00
2022-11-14 22:22:57 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
2022-11-08 15:58:44 +00:00
2022-11-14 22:22:57 +00:00
_ , err := client . User ( ctx , first . UserID . String ( ) )
require . NoError ( t , err , "" )
2022-11-08 15:58:44 +00:00
2022-11-14 22:22:57 +00:00
_ , err = client . CreateUser ( ctx , codersdk . CreateUserRequest {
Email : "alice@email.com" ,
Username : "alice" ,
2023-02-08 20:10:08 +00:00
Password : "MySecurePassword!" ,
2022-11-14 22:22:57 +00:00
OrganizationID : first . OrganizationID ,
} )
require . NoError ( t , err )
2022-11-08 15:58:44 +00:00
2022-11-14 22:22:57 +00:00
res , err := client . Users ( ctx , codersdk . UsersRequest { } )
require . NoError ( t , err )
require . Len ( t , res . Users , 2 )
require . Equal ( t , res . Count , 2 )
2022-11-08 15:58:44 +00:00
2022-11-14 22:22:57 +00:00
res , err = client . Users ( ctx , codersdk . UsersRequest {
Pagination : codersdk . Pagination {
Limit : 1 ,
} ,
} )
require . NoError ( t , err )
require . Len ( t , res . Users , 1 )
require . Equal ( t , res . Count , 2 )
2022-11-08 15:58:44 +00:00
2022-11-14 22:22:57 +00:00
res , err = client . Users ( ctx , codersdk . UsersRequest {
Pagination : codersdk . Pagination {
Offset : 1 ,
} ,
} )
require . NoError ( t , err )
require . Len ( t , res . Users , 1 )
require . Equal ( t , res . Count , 2 )
2022-11-08 15:58:44 +00:00
2022-11-14 22:22:57 +00:00
// if offset is higher than the count postgres returns an empty array
2023-07-26 14:33:48 +00:00
// and not an ErrNoRows error.
2022-11-14 22:22:57 +00:00
res , err = client . Users ( ctx , codersdk . UsersRequest {
Pagination : codersdk . Pagination {
Offset : 3 ,
} ,
2022-11-08 15:58:44 +00:00
} )
2022-11-14 22:22:57 +00:00
require . NoError ( t , err )
require . Len ( t , res . Users , 0 )
require . Equal ( t , res . Count , 0 )
2022-11-08 15:58:44 +00:00
}
2022-10-06 19:02:27 +00:00
func TestPostTokens ( t * testing . T ) {
2022-03-07 17:40:54 +00:00
t . Parallel ( )
2022-10-06 19:02:27 +00:00
client := coderdtest . New ( t , nil )
_ = coderdtest . CreateFirstUser ( t , client )
2022-08-09 17:17:00 +00:00
2022-10-06 19:02:27 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
2022-08-09 17:17:00 +00:00
2022-10-14 16:46:38 +00:00
apiKey , err := client . CreateToken ( ctx , codersdk . Me , codersdk . CreateTokenRequest { } )
2022-10-06 19:02:27 +00:00
require . NotNil ( t , apiKey )
require . GreaterOrEqual ( t , len ( apiKey . Key ) , 2 )
require . NoError ( t , err )
2022-01-20 13:46:51 +00:00
}
2022-01-25 01:09:39 +00:00
2022-05-10 02:38:20 +00:00
func TestWorkspacesByUser ( t * testing . T ) {
t . Parallel ( )
t . Run ( "Empty" , func ( t * testing . T ) {
t . Parallel ( )
client := coderdtest . New ( t , nil )
_ = coderdtest . CreateFirstUser ( t , client )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
2022-11-10 18:25:46 +00:00
res , err := client . Workspaces ( ctx , codersdk . WorkspaceFilter {
2022-05-18 15:09:07 +00:00
Owner : codersdk . Me ,
} )
2022-05-10 02:38:20 +00:00
require . NoError ( t , err )
2022-11-10 18:25:46 +00:00
require . Len ( t , res . Workspaces , 0 )
2022-05-10 02:38:20 +00:00
} )
t . Run ( "Access" , func ( t * testing . T ) {
t . Parallel ( )
2022-09-04 16:28:09 +00:00
client := coderdtest . New ( t , & coderdtest . Options { IncludeProvisionerDaemon : true } )
2022-05-10 02:38:20 +00:00
user := coderdtest . CreateFirstUser ( t , client )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
newUser , err := client . CreateUser ( ctx , codersdk . CreateUserRequest {
2022-05-10 02:38:20 +00:00
Email : "test@coder.com" ,
Username : "someone" ,
2023-02-08 20:10:08 +00:00
Password : "MySecurePassword!" ,
2022-05-10 02:38:20 +00:00
OrganizationID : user . OrganizationID ,
} )
require . NoError ( t , err )
2022-08-09 17:17:00 +00:00
auth , err := client . LoginWithPassword ( ctx , codersdk . LoginWithPasswordRequest {
2022-05-10 02:38:20 +00:00
Email : newUser . Email ,
2023-02-08 20:10:08 +00:00
Password : "MySecurePassword!" ,
2022-05-10 02:38:20 +00:00
} )
require . NoError ( t , err )
newUserClient := codersdk . New ( client . URL )
2022-11-09 13:31:24 +00:00
newUserClient . SetSessionToken ( auth . SessionToken )
2022-05-10 02:38:20 +00:00
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , nil )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2022-05-10 02:38:20 +00:00
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
2022-11-10 18:25:46 +00:00
res , err := newUserClient . Workspaces ( ctx , codersdk . WorkspaceFilter { Owner : codersdk . Me } )
2022-05-10 02:38:20 +00:00
require . NoError ( t , err )
2022-11-10 18:25:46 +00:00
require . Len ( t , res . Workspaces , 0 )
2022-05-10 02:38:20 +00:00
2022-11-10 18:25:46 +00:00
res , err = client . Workspaces ( ctx , codersdk . WorkspaceFilter { Owner : codersdk . Me } )
2022-05-10 02:38:20 +00:00
require . NoError ( t , err )
2022-11-10 18:25:46 +00:00
require . Len ( t , res . Workspaces , 1 )
2022-05-10 02:38:20 +00:00
} )
}
2023-08-02 14:31:25 +00:00
func TestDormantUser ( t * testing . T ) {
t . Parallel ( )
client := coderdtest . New ( t , & coderdtest . Options { IncludeProvisionerDaemon : true } )
user := coderdtest . CreateFirstUser ( t , client )
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
// Create a new user
newUser , err := client . CreateUser ( ctx , codersdk . CreateUserRequest {
Email : "test@coder.com" ,
Username : "someone" ,
Password : "MySecurePassword!" ,
OrganizationID : user . OrganizationID ,
} )
require . NoError ( t , err )
// User should be dormant as they haven't logged in yet
users , err := client . Users ( ctx , codersdk . UsersRequest { Search : newUser . Username } )
require . NoError ( t , err )
require . Len ( t , users . Users , 1 )
require . Equal ( t , codersdk . UserStatusDormant , users . Users [ 0 ] . Status )
// User logs in now
_ , err = client . LoginWithPassword ( ctx , codersdk . LoginWithPasswordRequest {
Email : newUser . Email ,
Password : "MySecurePassword!" ,
} )
require . NoError ( t , err )
// User status should be active now
users , err = client . Users ( ctx , codersdk . UsersRequest { Search : newUser . Username } )
require . NoError ( t , err )
require . Len ( t , users . Users , 1 )
require . Equal ( t , codersdk . UserStatusActive , users . Users [ 0 ] . Status )
}
2022-06-02 14:01:45 +00:00
// TestSuspendedPagination is when the after_id is a suspended record.
// The database query should still return the correct page, as the after_id
// is in a subquery that finds the record regardless of its status.
// This is mainly to confirm the db fake has the same behavior.
func TestSuspendedPagination ( t * testing . T ) {
t . Parallel ( )
2022-09-15 01:21:53 +00:00
t . Skip ( "This fails when two users are created at the exact same time. The reason is unknown... See: https://github.com/coder/coder/actions/runs/3057047622/jobs/4931863163" )
2023-01-05 18:05:20 +00:00
client := coderdtest . New ( t , nil )
2022-06-02 14:01:45 +00:00
coderdtest . CreateFirstUser ( t , client )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
2022-08-21 22:32:53 +00:00
t . Cleanup ( cancel )
2022-08-09 17:17:00 +00:00
me , err := client . User ( ctx , codersdk . Me )
2022-06-02 14:01:45 +00:00
require . NoError ( t , err )
orgID := me . OrganizationIDs [ 0 ]
total := 10
users := make ( [ ] codersdk . User , 0 , total )
// Create users
for i := 0 ; i < total ; i ++ {
email := fmt . Sprintf ( "%d@coder.com" , i )
username := fmt . Sprintf ( "user%d" , i )
2022-08-09 17:17:00 +00:00
user , err := client . CreateUser ( ctx , codersdk . CreateUserRequest {
2022-06-02 14:01:45 +00:00
Email : email ,
Username : username ,
2023-02-08 20:10:08 +00:00
Password : "MySecurePassword!" ,
2022-06-02 14:01:45 +00:00
OrganizationID : orgID ,
} )
require . NoError ( t , err )
users = append ( users , user )
}
sortUsers ( users )
deletedUser := users [ 2 ]
expected := users [ 3 : 8 ]
_ , err = client . UpdateUserStatus ( ctx , deletedUser . ID . String ( ) , codersdk . UserStatusSuspended )
require . NoError ( t , err , "suspend user" )
page , err := client . Users ( ctx , codersdk . UsersRequest {
Pagination : codersdk . Pagination {
Limit : len ( expected ) ,
AfterID : deletedUser . ID ,
} ,
} )
require . NoError ( t , err )
2022-11-14 22:22:57 +00:00
require . Equal ( t , expected , page . Users , "expected page" )
2022-06-02 14:01:45 +00:00
}
2024-01-30 22:02:21 +00:00
func TestUserAutofillParameters ( t * testing . T ) {
t . Parallel ( )
t . Run ( "NotSelf" , func ( t * testing . T ) {
t . Parallel ( )
client1 , _ , api := coderdtest . NewWithAPI ( t , & coderdtest . Options { } )
u1 := coderdtest . CreateFirstUser ( t , client1 )
client2 , u2 := coderdtest . CreateAnotherUser ( t , client1 , u1 . OrganizationID )
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
db := api . Database
version := dbfake . TemplateVersion ( t , db ) . Seed ( database . TemplateVersion {
CreatedBy : u1 . UserID ,
OrganizationID : u1 . OrganizationID ,
} ) . Params ( database . TemplateVersionParameter {
Name : "param" ,
Required : true ,
} ) . Do ( )
_ , err := client2 . UserAutofillParameters (
ctx ,
u1 . UserID . String ( ) ,
version . Template . ID ,
)
var apiErr * codersdk . Error
require . ErrorAs ( t , err , & apiErr )
require . Equal ( t , http . StatusBadRequest , apiErr . StatusCode ( ) )
// u1 should be able to read u2's parameters as u1 is site admin.
_ , err = client1 . UserAutofillParameters (
ctx ,
u2 . ID . String ( ) ,
version . Template . ID ,
)
require . NoError ( t , err )
} )
t . Run ( "FindsParameters" , func ( t * testing . T ) {
t . Parallel ( )
client1 , _ , api := coderdtest . NewWithAPI ( t , & coderdtest . Options { } )
u1 := coderdtest . CreateFirstUser ( t , client1 )
client2 , u2 := coderdtest . CreateAnotherUser ( t , client1 , u1 . OrganizationID )
db := api . Database
version := dbfake . TemplateVersion ( t , db ) . Seed ( database . TemplateVersion {
CreatedBy : u1 . UserID ,
OrganizationID : u1 . OrganizationID ,
} ) . Params ( database . TemplateVersionParameter {
Name : "param" ,
Required : true ,
} ,
database . TemplateVersionParameter {
Name : "param2" ,
Ephemeral : true ,
} ,
) . Do ( )
dbfake . WorkspaceBuild ( t , db , database . Workspace {
OwnerID : u2 . ID ,
TemplateID : version . Template . ID ,
OrganizationID : u1 . OrganizationID ,
} ) . Params (
database . WorkspaceBuildParameter {
Name : "param" ,
Value : "foo" ,
} ,
database . WorkspaceBuildParameter {
Name : "param2" ,
Value : "bar" ,
} ,
) . Do ( )
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
// Use client2 since client1 is site admin, so
// we don't get good coverage on RBAC working.
params , err := client2 . UserAutofillParameters (
ctx ,
u2 . ID . String ( ) ,
version . Template . ID ,
)
require . NoError ( t , err )
require . Equal ( t , 1 , len ( params ) )
require . Equal ( t , "param" , params [ 0 ] . Name )
require . Equal ( t , "foo" , params [ 0 ] . Value )
// Verify that latest parameter value is returned.
dbfake . WorkspaceBuild ( t , db , database . Workspace {
OrganizationID : u1 . OrganizationID ,
OwnerID : u2 . ID ,
TemplateID : version . Template . ID ,
} ) . Params (
database . WorkspaceBuildParameter {
Name : "param" ,
Value : "foo_new" ,
} ,
) . Do ( )
params , err = client2 . UserAutofillParameters (
ctx ,
u2 . ID . String ( ) ,
version . Template . ID ,
)
require . NoError ( t , err )
require . Equal ( t , 1 , len ( params ) )
require . Equal ( t , "param" , params [ 0 ] . Name )
require . Equal ( t , "foo_new" , params [ 0 ] . Value )
} )
}
2022-04-22 20:27:55 +00:00
// TestPaginatedUsers creates a list of users, then tries to paginate through
// them using different page sizes.
func TestPaginatedUsers ( t * testing . T ) {
t . Parallel ( )
2023-12-04 16:05:17 +00:00
client , db := coderdtest . NewWithDatabase ( t , nil )
2022-04-22 20:27:55 +00:00
coderdtest . CreateFirstUser ( t , client )
2022-08-09 17:17:00 +00:00
// This test takes longer than a long time.
2023-03-21 14:10:22 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong * 4 )
2022-08-21 22:32:53 +00:00
t . Cleanup ( cancel )
2022-08-09 17:17:00 +00:00
me , err := client . User ( ctx , codersdk . Me )
2022-04-22 20:27:55 +00:00
require . NoError ( t , err )
2023-03-21 14:10:22 +00:00
// When 50 users exist
total := 50
2023-12-04 16:05:17 +00:00
allUsers := make ( [ ] database . User , total + 1 )
allUsers [ 0 ] = database . User {
Email : me . Email ,
Username : me . Username ,
}
specialUsers := make ( [ ] database . User , total / 2 )
2022-08-09 17:17:00 +00:00
2023-12-04 16:05:17 +00:00
eg , _ := errgroup . WithContext ( ctx )
2022-04-22 20:27:55 +00:00
// Create users
for i := 0 ; i < total ; i ++ {
2022-08-09 17:17:00 +00:00
i := i
eg . Go ( func ( ) error {
email := fmt . Sprintf ( "%d@coder.com" , i )
username := fmt . Sprintf ( "user%d" , i )
if i % 2 == 0 {
email = fmt . Sprintf ( "%d@gmail.com" , i )
username = fmt . Sprintf ( "specialuser%d" , i )
}
2023-06-06 10:37:41 +00:00
if i % 3 == 0 {
username = strings . ToUpper ( username )
}
2023-12-04 16:05:17 +00:00
// We used to use the API to ceate users, but that is slow.
// Instead, we create them directly in the database now
// to prevent timeout flakes.
newUser := dbgen . User ( t , db , database . User {
Email : email ,
Username : username ,
2022-08-09 17:17:00 +00:00
} )
allUsers [ i + 1 ] = newUser
if i % 2 == 0 {
specialUsers [ i / 2 ] = newUser
}
return nil
2022-04-22 20:27:55 +00:00
} )
}
2022-08-09 17:17:00 +00:00
err = eg . Wait ( )
require . NoError ( t , err , "create users failed" )
2022-04-22 20:27:55 +00:00
2023-06-06 06:47:59 +00:00
// Sorting the users will sort by username.
2023-12-04 16:05:17 +00:00
sortDatabaseUsers ( allUsers )
sortDatabaseUsers ( specialUsers )
2022-04-25 15:27:08 +00:00
2022-04-22 20:27:55 +00:00
gmailSearch := func ( request codersdk . UsersRequest ) codersdk . UsersRequest {
request . Search = "gmail"
return request
}
usernameSearch := func ( request codersdk . UsersRequest ) codersdk . UsersRequest {
request . Search = "specialuser"
return request
}
2022-08-09 17:17:00 +00:00
tests := [ ] struct {
name string
limit int
2023-12-04 16:05:17 +00:00
allUsers [ ] database . User
2022-08-09 17:17:00 +00:00
opt func ( request codersdk . UsersRequest ) codersdk . UsersRequest
} {
{ name : "all users" , limit : 10 , allUsers : allUsers } ,
{ name : "all users" , limit : 5 , allUsers : allUsers } ,
{ name : "all users" , limit : 3 , allUsers : allUsers } ,
{ name : "gmail search" , limit : 3 , allUsers : specialUsers , opt : gmailSearch } ,
{ name : "gmail search" , limit : 7 , allUsers : specialUsers , opt : gmailSearch } ,
{ name : "username search" , limit : 3 , allUsers : specialUsers , opt : usernameSearch } ,
{ name : "username search" , limit : 3 , allUsers : specialUsers , opt : usernameSearch } ,
}
for _ , tt := range tests {
tt := tt
t . Run ( fmt . Sprintf ( "%s %d" , tt . name , tt . limit ) , func ( t * testing . T ) {
t . Parallel ( )
// This test takes longer than a long time.
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong * 2 )
defer cancel ( )
assertPagination ( ctx , t , client , tt . limit , tt . allUsers , tt . opt )
} )
}
2022-04-22 20:27:55 +00:00
}
// Assert pagination will page through the list of all users using the given
// limit for each page. The 'allUsers' is the expected full list to compare
// against.
2023-12-04 16:05:17 +00:00
func assertPagination ( ctx context . Context , t * testing . T , client * codersdk . Client , limit int , allUsers [ ] database . User ,
2022-08-09 17:17:00 +00:00
opt func ( request codersdk . UsersRequest ) codersdk . UsersRequest ,
) {
2022-04-22 20:27:55 +00:00
var count int
if opt == nil {
opt = func ( request codersdk . UsersRequest ) codersdk . UsersRequest {
return request
}
}
// Check the first page
page , err := client . Users ( ctx , opt ( codersdk . UsersRequest {
2022-05-10 07:44:09 +00:00
Pagination : codersdk . Pagination {
Limit : limit ,
} ,
2022-04-22 20:27:55 +00:00
} ) )
require . NoError ( t , err , "first page" )
2023-12-04 16:05:17 +00:00
require . Equalf ( t , onlyUsernames ( page . Users ) , onlyUsernames ( allUsers [ : limit ] ) , "first page, limit=%d" , limit )
2022-11-14 22:22:57 +00:00
count += len ( page . Users )
2022-04-22 20:27:55 +00:00
for {
2022-11-14 22:22:57 +00:00
if len ( page . Users ) == 0 {
2022-04-22 20:27:55 +00:00
break
}
2022-11-14 22:22:57 +00:00
afterCursor := page . Users [ len ( page . Users ) - 1 ] . ID
2022-04-22 20:27:55 +00:00
// Assert each page is the next expected page
// This is using a cursor, and only works if all users created_at
// is unique.
page , err = client . Users ( ctx , opt ( codersdk . UsersRequest {
2022-05-10 07:44:09 +00:00
Pagination : codersdk . Pagination {
Limit : limit ,
AfterID : afterCursor ,
} ,
2022-04-22 20:27:55 +00:00
} ) )
require . NoError ( t , err , "next cursor page" )
// Also check page by offset
offsetPage , err := client . Users ( ctx , opt ( codersdk . UsersRequest {
2022-05-10 07:44:09 +00:00
Pagination : codersdk . Pagination {
Limit : limit ,
Offset : count ,
} ,
2022-04-22 20:27:55 +00:00
} ) )
require . NoError ( t , err , "next offset page" )
2023-12-04 16:05:17 +00:00
var expected [ ] database . User
2022-04-22 20:27:55 +00:00
if count + limit > len ( allUsers ) {
expected = allUsers [ count : ]
} else {
expected = allUsers [ count : count + limit ]
}
2023-12-04 16:05:17 +00:00
require . Equalf ( t , onlyUsernames ( page . Users ) , onlyUsernames ( expected ) , "next users, after=%s, limit=%d" , afterCursor , limit )
require . Equalf ( t , onlyUsernames ( offsetPage . Users ) , onlyUsernames ( expected ) , "offset users, offset=%d, limit=%d" , count , limit )
2022-04-22 20:27:55 +00:00
// Also check the before
prevPage , err := client . Users ( ctx , opt ( codersdk . UsersRequest {
2022-05-10 07:44:09 +00:00
Pagination : codersdk . Pagination {
Offset : count - limit ,
Limit : limit ,
} ,
2022-04-22 20:27:55 +00:00
} ) )
require . NoError ( t , err , "prev page" )
2023-12-04 16:05:17 +00:00
require . Equal ( t , onlyUsernames ( allUsers [ count - limit : count ] ) , onlyUsernames ( prevPage . Users ) , "prev users" )
2022-11-14 22:22:57 +00:00
count += len ( page . Users )
2022-04-22 20:27:55 +00:00
}
}
2022-04-25 15:27:08 +00:00
// sortUsers sorts by (created_at, id)
func sortUsers ( users [ ] codersdk . User ) {
2023-08-09 19:50:26 +00:00
slices . SortFunc ( users , func ( a , b codersdk . User ) int {
return slice . Ascending ( strings . ToLower ( a . Username ) , strings . ToLower ( b . Username ) )
2022-04-25 15:27:08 +00:00
} )
}
2023-05-08 13:59:01 +00:00
2023-12-04 16:05:17 +00:00
func sortDatabaseUsers ( users [ ] database . User ) {
slices . SortFunc ( users , func ( a , b database . User ) int {
return slice . Ascending ( strings . ToLower ( a . Username ) , strings . ToLower ( b . Username ) )
} )
}
func onlyUsernames [ U codersdk . User | database . User ] ( users [ ] U ) [ ] string {
var out [ ] string
for _ , u := range users {
switch u := ( any ( u ) ) . ( type ) {
case codersdk . User :
out = append ( out , u . Username )
case database . User :
out = append ( out , u . Username )
}
}
return out
}
2023-05-08 13:59:01 +00:00
func BenchmarkUsersMe ( b * testing . B ) {
client := coderdtest . New ( b , nil )
_ = coderdtest . CreateFirstUser ( b , client )
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
b . ReportAllocs ( )
b . ResetTimer ( )
for i := 0 ; i < b . N ; i ++ {
_ , err := client . User ( ctx , codersdk . Me )
require . NoError ( b , err )
}
}