Skip to content

Commit 8856584

Browse files
committed
Add new user test
1 parent dfeb19c commit 8856584

File tree

8 files changed

+196
-17
lines changed

8 files changed

+196
-17
lines changed

coderd/coderd.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,14 @@ func New(options *Options) http.Handler {
3939
})
4040
r.Post("/login", users.loginWithPassword)
4141
r.Post("/logout", users.logout)
42+
r.Post("/user", users.createInitialUser)
4243
r.Route("/users", func(r chi.Router) {
43-
r.Post("/", users.createInitialUser)
44-
44+
r.Use(
45+
httpmw.ExtractAPIKey(options.Database, nil),
46+
)
47+
r.Post("/", users.createUser)
4548
r.Group(func(r chi.Router) {
46-
r.Use(
47-
httpmw.ExtractAPIKey(options.Database, nil),
48-
httpmw.ExtractUserParam(options.Database),
49-
)
49+
r.Use(httpmw.ExtractUserParam(options.Database))
5050
r.Get("/{user}", users.user)
5151
r.Get("/{user}/organizations", users.userOrganizations)
5252
})
@@ -79,6 +79,7 @@ func New(options *Options) http.Handler {
7979
r.Get("/", workspaces.listAllWorkspaces)
8080
r.Route("/{user}", func(r chi.Router) {
8181
r.Use(httpmw.ExtractUserParam(options.Database))
82+
r.Get("/", workspaces.listAllWorkspaces)
8283
r.Post("/", workspaces.createWorkspaceForUser)
8384
r.Route("/{workspace}", func(r chi.Router) {
8485
r.Use(httpmw.ExtractWorkspaceParam(options.Database))

coderd/users.go

Lines changed: 72 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,31 @@ import (
1919
"github.com/coder/coder/httpmw"
2020
)
2121

22-
// User is the JSON representation of a Coder user.
22+
// User represents a user in Coder.
2323
type User struct {
2424
ID string `json:"id" validate:"required"`
2525
Email string `json:"email" validate:"required"`
2626
CreatedAt time.Time `json:"created_at" validate:"required"`
2727
Username string `json:"username" validate:"required"`
2828
}
2929

30-
// CreateInitialUserRequest enables callers to create a new user.
30+
// CreateInitialUserRequest provides options to create the initial
31+
// user for a Coder deployment. The organization provided will be
32+
// created as well.
3133
type CreateInitialUserRequest struct {
3234
Email string `json:"email" validate:"required,email"`
3335
Username string `json:"username" validate:"required,username"`
3436
Password string `json:"password" validate:"required"`
3537
Organization string `json:"organization" validate:"required,username"`
3638
}
3739

40+
// CreateUserRequest provides options for creating a new user.
41+
type CreateUserRequest struct {
42+
Email string `json:"email" validate:"required,email"`
43+
Username string `json:"username" validate:"required,username"`
44+
Password string `json:"password" validate:"required"`
45+
}
46+
3847
// LoginWithPasswordRequest enables callers to authenticate with email and password.
3948
type LoginWithPasswordRequest struct {
4049
Email string `json:"email" validate:"required,email"`
@@ -123,20 +132,65 @@ func (users *users) createInitialUser(rw http.ResponseWriter, r *http.Request) {
123132
}
124133

125134
render.Status(r, http.StatusCreated)
126-
render.JSON(rw, r, user)
135+
render.JSON(rw, r, convertUser(user))
136+
}
137+
138+
func (users *users) createUser(rw http.ResponseWriter, r *http.Request) {
139+
var createUser CreateUserRequest
140+
if !httpapi.Read(rw, r, &createUser) {
141+
return
142+
}
143+
_, err := users.Database.GetUserByEmailOrUsername(r.Context(), database.GetUserByEmailOrUsernameParams{
144+
Username: createUser.Username,
145+
Email: createUser.Email,
146+
})
147+
if err == nil {
148+
httpapi.Write(rw, http.StatusConflict, httpapi.Response{
149+
Message: "user already exists",
150+
})
151+
return
152+
}
153+
if !errors.Is(err, sql.ErrNoRows) {
154+
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
155+
Message: fmt.Sprintf("get user: %s", err),
156+
})
157+
return
158+
}
159+
160+
hashedPassword, err := userpassword.Hash(createUser.Password)
161+
if err != nil {
162+
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
163+
Message: fmt.Sprintf("hash password: %s", err.Error()),
164+
})
165+
return
166+
}
167+
168+
user, err := users.Database.InsertUser(r.Context(), database.InsertUserParams{
169+
ID: uuid.NewString(),
170+
Email: createUser.Email,
171+
HashedPassword: []byte(hashedPassword),
172+
Username: createUser.Username,
173+
LoginType: database.LoginTypeBuiltIn,
174+
CreatedAt: database.Now(),
175+
UpdatedAt: database.Now(),
176+
})
177+
if err != nil {
178+
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
179+
Message: fmt.Sprintf("create user: %s", err.Error()),
180+
})
181+
return
182+
}
183+
184+
render.Status(r, http.StatusCreated)
185+
render.JSON(rw, r, convertUser(user))
127186
}
128187

129188
// Returns the parameterized user requested. All validation
130189
// is completed in the middleware for this route.
131190
func (*users) user(rw http.ResponseWriter, r *http.Request) {
132191
user := httpmw.UserParam(r)
133192

134-
render.JSON(rw, r, User{
135-
ID: user.ID,
136-
Email: user.Email,
137-
CreatedAt: user.CreatedAt,
138-
Username: user.Username,
139-
})
193+
render.JSON(rw, r, convertUser(user))
140194
}
141195

142196
// Returns organizations the parameterized user has access to.
@@ -265,3 +319,12 @@ func generateAPIKeyIDSecret() (id string, secret string, err error) {
265319
}
266320
return id, secret, nil
267321
}
322+
323+
func convertUser(user database.User) User {
324+
return User{
325+
ID: user.ID,
326+
Email: user.Email,
327+
CreatedAt: user.CreatedAt,
328+
Username: user.Username,
329+
}
330+
}

coderd/users_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,30 @@ func TestUsers(t *testing.T) {
7676
require.NoError(t, err)
7777
require.Len(t, orgs, 1)
7878
})
79+
80+
t.Run("CreateUser", func(t *testing.T) {
81+
t.Parallel()
82+
server := coderdtest.New(t)
83+
_ = server.RandomInitialUser(t)
84+
_, err := server.Client.CreateUser(context.Background(), coderd.CreateUserRequest{
85+
Email: "wow@ok.io",
86+
Username: "tomato",
87+
Password: "bananas",
88+
})
89+
require.NoError(t, err)
90+
})
91+
92+
t.Run("CreateUserConflict", func(t *testing.T) {
93+
t.Parallel()
94+
server := coderdtest.New(t)
95+
user := server.RandomInitialUser(t)
96+
_, err := server.Client.CreateUser(context.Background(), coderd.CreateUserRequest{
97+
Email: "wow@ok.io",
98+
Username: user.Username,
99+
Password: "bananas",
100+
})
101+
require.NoError(t, err)
102+
})
79103
}
80104

81105
func TestLogout(t *testing.T) {

coderd/workspaces_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,46 @@ func TestWorkspaces(t *testing.T) {
109109
require.Error(t, err)
110110
})
111111

112+
t.Run("CreateInvalidProject", func(t *testing.T) {
113+
t.Parallel()
114+
server := coderdtest.New(t)
115+
_ = server.RandomInitialUser(t)
116+
_, err := server.Client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{
117+
ProjectID: uuid.New(),
118+
Name: "moo",
119+
})
120+
require.Error(t, err)
121+
})
122+
123+
t.Run("CreateNotInProjectOrganization", func(t *testing.T) {
124+
t.Parallel()
125+
server := coderdtest.New(t)
126+
initial := server.RandomInitialUser(t)
127+
project, err := server.Client.CreateProject(context.Background(), initial.Organization, coderd.CreateProjectRequest{
128+
Name: "banana",
129+
Provisioner: database.ProvisionerTypeTerraform,
130+
})
131+
require.NoError(t, err)
132+
_, err = server.Client.CreateUser(context.Background(), coderd.CreateUserRequest{
133+
Email: "hello@ok.io",
134+
Username: "example",
135+
Password: "wowowow",
136+
})
137+
require.NoError(t, err)
138+
token, err := server.Client.LoginWithPassword(context.Background(), coderd.LoginWithPasswordRequest{
139+
Email: "hello@ok.io",
140+
Password: "wowowow",
141+
})
142+
require.NoError(t, err)
143+
err = server.Client.SetSessionToken(token.SessionToken)
144+
require.NoError(t, err)
145+
_, err = server.Client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{
146+
ProjectID: project.ID,
147+
Name: "moo",
148+
})
149+
require.Error(t, err)
150+
})
151+
112152
t.Run("CreateAlreadyExists", func(t *testing.T) {
113153
t.Parallel()
114154
server := coderdtest.New(t)

codersdk/users.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,20 @@ import (
1313
// This initial user has superadmin privileges. If >0 users exist, this request
1414
// will fail.
1515
func (c *Client) CreateInitialUser(ctx context.Context, req coderd.CreateInitialUserRequest) (coderd.User, error) {
16+
res, err := c.request(ctx, http.MethodPost, "/api/v2/user", req)
17+
if err != nil {
18+
return coderd.User{}, err
19+
}
20+
defer res.Body.Close()
21+
if res.StatusCode != http.StatusCreated {
22+
return coderd.User{}, readBodyAsError(res)
23+
}
24+
var user coderd.User
25+
return user, json.NewDecoder(res.Body).Decode(&user)
26+
}
27+
28+
// CreateUser creates a new user.
29+
func (c *Client) CreateUser(ctx context.Context, req coderd.CreateUserRequest) (coderd.User, error) {
1630
res, err := c.request(ctx, http.MethodPost, "/api/v2/users", req)
1731
if err != nil {
1832
return coderd.User{}, err

codersdk/users_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,16 @@ func TestUsers(t *testing.T) {
5555
err := server.Client.Logout(context.Background())
5656
require.NoError(t, err)
5757
})
58+
59+
t.Run("CreateMultiple", func(t *testing.T) {
60+
t.Parallel()
61+
server := coderdtest.New(t)
62+
_ = server.RandomInitialUser(t)
63+
_, err := server.Client.CreateUser(context.Background(), coderd.CreateUserRequest{
64+
Email: "wow@ok.io",
65+
Username: "example",
66+
Password: "tomato",
67+
})
68+
require.NoError(t, err)
69+
})
5870
}

codersdk/workspaces_test.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,25 @@ func TestWorkspaces(t *testing.T) {
2828
require.Error(t, err)
2929
})
3030

31-
t.Run("List", func(t *testing.T) {
31+
t.Run("ListByUser", func(t *testing.T) {
32+
t.Parallel()
33+
server := coderdtest.New(t)
34+
user := server.RandomInitialUser(t)
35+
project, err := server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{
36+
Name: "tomato",
37+
Provisioner: database.ProvisionerTypeTerraform,
38+
})
39+
require.NoError(t, err)
40+
_, err = server.Client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{
41+
Name: "wooow",
42+
ProjectID: project.ID,
43+
})
44+
require.NoError(t, err)
45+
_, err = server.Client.WorkspacesByUser(context.Background(), "me")
46+
require.NoError(t, err)
47+
})
48+
49+
t.Run("ListByProject", func(t *testing.T) {
3250
t.Parallel()
3351
server := coderdtest.New(t)
3452
user := server.RandomInitialUser(t)
@@ -46,6 +64,13 @@ func TestWorkspaces(t *testing.T) {
4664
require.NoError(t, err)
4765
})
4866

67+
t.Run("ListByProjectError", func(t *testing.T) {
68+
t.Parallel()
69+
server := coderdtest.New(t)
70+
_, err := server.Client.WorkspacesByProject(context.Background(), "", "")
71+
require.Error(t, err)
72+
})
73+
4974
t.Run("CreateError", func(t *testing.T) {
5075
t.Parallel()
5176
server := coderdtest.New(t)

database/databasefake/databasefake.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ func (q *fakeQuerier) GetProjectByID(_ context.Context, id uuid.UUID) (database.
213213
return project, nil
214214
}
215215
}
216-
return database.Project{}, nil
216+
return database.Project{}, sql.ErrNoRows
217217
}
218218

219219
func (q *fakeQuerier) GetProjectByOrganizationAndName(_ context.Context, arg database.GetProjectByOrganizationAndNameParams) (database.Project, error) {

0 commit comments

Comments
 (0)