Skip to content

Commit f691c43

Browse files
authored
Merge pull request #40 from brokenhandsio/fixes-after-testing
Fixes after testing
2 parents 5292b62 + 7ee4a69 commit f691c43

File tree

2 files changed

+158
-42
lines changed

2 files changed

+158
-42
lines changed

Sources/SteamPress/Controllers/Admin/UserAdminController.swift

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ struct UserAdminController: RouteCollection {
3232
return try validateUserCreation(data, on: req).flatMap { createUserErrors in
3333
if let errors = createUserErrors {
3434
let presenter = try req.make(BlogAdminPresenter.self)
35-
let view = try presenter.createUserView(on: req, editing: true, errors: errors.errors, name: data.name, nameError: errors.nameError, username: data.username, usernameErorr: errors.usernameError, passwordError: errors.passwordError, confirmPasswordError: errors.confirmPasswordError, resetPasswordOnLogin: data.resetPasswordOnLogin ?? false, userID: nil, profilePicture: data.profilePicture, twitterHandle: data.twitterHandle, biography: data.biography, tagline: data.tagline, pageInformation: req.adminPageInfomation())
35+
let view = try presenter.createUserView(on: req, editing: false, errors: errors.errors, name: data.name, nameError: errors.nameError, username: data.username, usernameErorr: errors.usernameError, passwordError: errors.passwordError, confirmPasswordError: errors.confirmPasswordError, resetPasswordOnLogin: data.resetPasswordOnLogin ?? false, userID: nil, profilePicture: data.profilePicture, twitterHandle: data.twitterHandle, biography: data.biography, tagline: data.tagline, pageInformation: req.adminPageInfomation())
3636
return try view.encode(for: req)
3737
}
3838

@@ -42,7 +42,11 @@ struct UserAdminController: RouteCollection {
4242

4343
let hasher = try req.make(PasswordHasher.self)
4444
let hashedPassword = try hasher.hash(password)
45-
let newUser = BlogUser(name: name, username: username.lowercased(), password: hashedPassword, profilePicture: data.profilePicture, twitterHandle: data.twitterHandle, biography: data.biography, tagline: data.tagline)
45+
let profilePicture = data.profilePicture.isEmptyOrWhitespace() ? nil : data.profilePicture
46+
let twitterHandle = data.twitterHandle.isEmptyOrWhitespace() ? nil : data.twitterHandle
47+
let biography = data.biography.isEmptyOrWhitespace() ? nil : data.biography
48+
let tagline = data.tagline.isEmptyOrWhitespace() ? nil : data.tagline
49+
let newUser = BlogUser(name: name, username: username.lowercased(), password: hashedPassword, profilePicture: profilePicture, twitterHandle: twitterHandle, biography: biography, tagline: tagline)
4650
if let resetPasswordRequired = data.resetPasswordOnLogin, resetPasswordRequired {
4751
newUser.resetPasswordRequired = true
4852
}
@@ -69,25 +73,31 @@ struct UserAdminController: RouteCollection {
6973
throw Abort(.internalServerError)
7074
}
7175

72-
return try self.validateUserCreation(data, editing: true, on: req).flatMap { errors in
76+
return try self.validateUserCreation(data, editing: true, existingUsername: user.username, on: req).flatMap { errors in
7377
if let editUserErrors = errors {
7478
let presenter = try req.make(BlogAdminPresenter.self)
75-
let view = try presenter.createUserView(on: req, editing: true, errors: editUserErrors.errors, name: data.name, nameError: errors?.nameError ?? false, username: data.username, usernameErorr: errors?.usernameError ?? false, passwordError: editUserErrors.passwordError, confirmPasswordError: editUserErrors.confirmPasswordError, resetPasswordOnLogin: data.resetPasswordOnLogin ?? false, userID: nil, profilePicture: data.profilePicture, twitterHandle: data.twitterHandle, biography: data.biography, tagline: data.tagline, pageInformation: req.adminPageInfomation())
79+
let view = try presenter.createUserView(on: req, editing: true, errors: editUserErrors.errors, name: data.name, nameError: errors?.nameError ?? false, username: data.username, usernameErorr: errors?.usernameError ?? false, passwordError: editUserErrors.passwordError, confirmPasswordError: editUserErrors.confirmPasswordError, resetPasswordOnLogin: data.resetPasswordOnLogin ?? false, userID: user.userID, profilePicture: data.profilePicture, twitterHandle: data.twitterHandle, biography: data.biography, tagline: data.tagline, pageInformation: req.adminPageInfomation())
7680
return try view.encode(for: req)
7781
}
7882

7983
user.name = name
8084
user.username = username.lowercased()
81-
user.profilePicture = data.profilePicture
82-
user.twitterHandle = data.twitterHandle
83-
user.biography = data.biography
84-
user.tagline = data.tagline
85+
86+
let profilePicture = data.profilePicture.isEmptyOrWhitespace() ? nil : data.profilePicture
87+
let twitterHandle = data.twitterHandle.isEmptyOrWhitespace() ? nil : data.twitterHandle
88+
let biography = data.biography.isEmptyOrWhitespace() ? nil : data.biography
89+
let tagline = data.tagline.isEmptyOrWhitespace() ? nil : data.tagline
90+
91+
user.profilePicture = profilePicture
92+
user.twitterHandle = twitterHandle
93+
user.biography = biography
94+
user.tagline = tagline
8595

8696
if let resetPasswordOnLogin = data.resetPasswordOnLogin, resetPasswordOnLogin {
8797
user.resetPasswordRequired = true
8898
}
8999

90-
if let password = data.password {
100+
if let password = data.password, password != "" {
91101
let hasher = try req.make(PasswordHasher.self)
92102
user.password = try hasher.hash(password)
93103
}
@@ -127,7 +137,7 @@ struct UserAdminController: RouteCollection {
127137
}
128138

129139
// MARK: - Validators
130-
private func validateUserCreation(_ data: CreateUserData, editing: Bool = false, on req: Request) throws -> EventLoopFuture<CreateUserErrors?> {
140+
private func validateUserCreation(_ data: CreateUserData, editing: Bool = false, existingUsername: String? = nil, on req: Request) throws -> EventLoopFuture<CreateUserErrors?> {
131141
var createUserErrors = [String]()
132142
var passwordError = false
133143
var confirmPasswordError = false
@@ -144,7 +154,7 @@ struct UserAdminController: RouteCollection {
144154
usernameError = true
145155
}
146156

147-
if !editing || data.password != nil {
157+
if !editing || !data.password.isEmptyOrWhitespace() {
148158
if data.password.isEmptyOrWhitespace() {
149159
createUserErrors.append("You must specify a password")
150160
passwordError = true
@@ -156,7 +166,7 @@ struct UserAdminController: RouteCollection {
156166
}
157167
}
158168

159-
if let password = data.password {
169+
if let password = data.password, password != "" {
160170
if password.count < 10 {
161171
createUserErrors.append("Your password must be at least 10 characters long")
162172
passwordError = true
@@ -177,14 +187,17 @@ struct UserAdminController: RouteCollection {
177187
}
178188

179189
var usernameUniqueError: EventLoopFuture<String?>
180-
181190
let usersRepository = try req.make(BlogUserRepository.self)
182191
if let username = data.username {
183-
usernameUniqueError = usersRepository.getUser(username: username.lowercased(), on: req).map { user in
184-
if user != nil {
185-
return "Sorry that username has already been taken"
186-
} else {
187-
return nil
192+
if editing && data.username == existingUsername {
193+
usernameUniqueError = req.future(nil)
194+
} else {
195+
usernameUniqueError = usersRepository.getUser(username: username.lowercased(), on: req).map { user in
196+
if user != nil {
197+
return "Sorry that username has already been taken"
198+
} else {
199+
return nil
200+
}
188201
}
189202
}
190203
} else {

Tests/SteamPressTests/AdminTests/AdminUserTests.swift

Lines changed: 127 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,31 @@ class AdminUserTests: XCTestCase {
7979
XCTAssertEqual(response.http.status, .seeOther)
8080
XCTAssertEqual(response.http.headers[.location].first, "/admin/")
8181
}
82+
83+
func testUserHasNoAdditionalInfoIfEmptyStringsSent() throws {
84+
struct CreateUserData: Content {
85+
static let defaultContentType = MediaType.urlEncodedForm
86+
let name = "Luke"
87+
let username = "lukes"
88+
let password = "somepassword"
89+
let confirmPassword = "somepassword"
90+
let profilePicture = ""
91+
let tagline = ""
92+
let biography = ""
93+
let twitterHandle = ""
94+
}
95+
96+
let createData = CreateUserData()
97+
_ = try testWorld.getResponse(to: createUserPath, body: createData, loggedInUser: user)
98+
99+
// First is user created in setup, final is one just created
100+
XCTAssertEqual(testWorld.context.repository.users.count, 2)
101+
let user = try XCTUnwrap(testWorld.context.repository.users.last)
102+
XCTAssertNil(user.profilePicture)
103+
XCTAssertNil(user.tagline)
104+
XCTAssertNil(user.biography)
105+
XCTAssertNil(user.twitterHandle)
106+
}
82107

83108
func testUserMustResetPasswordIfSetToWhenCreatingUser() throws {
84109
struct CreateUserResetData: Content {
@@ -264,6 +289,8 @@ class AdminUserTests: XCTestCase {
264289
XCTAssertTrue(viewErrors.contains("Your password must be at least 10 characters long"))
265290
let passwordError = try XCTUnwrap(presenter.createUserPasswordError)
266291
XCTAssertTrue(passwordError)
292+
let isEditing = try XCTUnwrap(presenter.createUserEditing)
293+
XCTAssertFalse(isEditing)
267294
}
268295

269296
func testUserCannotBeCreatedWithEmptyName() throws {
@@ -385,6 +412,25 @@ class AdminUserTests: XCTestCase {
385412
XCTAssertEqual(response.http.status, .seeOther)
386413
XCTAssertEqual(response.http.headers[.location].first, "/admin/")
387414
}
415+
416+
func testUserCanBeUpdatedWithSameUsername() throws {
417+
struct EditUserData: Content {
418+
static let defaultContentType = MediaType.urlEncodedForm
419+
let name = "Leia Organa"
420+
let username = "leia"
421+
}
422+
423+
let editData = EditUserData()
424+
let response = try testWorld.getResponse(to: "/admin/users/\(user.userID!)/edit", body: editData, loggedInUser: user)
425+
426+
XCTAssertEqual(testWorld.context.repository.users.count, 1)
427+
let updatedUser = try XCTUnwrap(testWorld.context.repository.users.last)
428+
XCTAssertEqual(updatedUser.username, editData.username)
429+
XCTAssertEqual(updatedUser.name, editData.name)
430+
XCTAssertEqual(updatedUser.userID, user.userID)
431+
XCTAssertEqual(response.http.status, .seeOther)
432+
XCTAssertEqual(response.http.headers[.location].first, "/admin/")
433+
}
388434

389435
func testUserCanBeUpdatedWithAllInformation() throws {
390436
struct EditUserData: Content {
@@ -412,6 +458,65 @@ class AdminUserTests: XCTestCase {
412458
XCTAssertEqual(response.http.status, .seeOther)
413459
XCTAssertEqual(response.http.headers[.location].first, "/admin/")
414460
}
461+
462+
func testOptionalInfoDoesntGetUpdatedWhenEditingUsernameAndSendingEmptyValuesIfSomeAlreadySet() throws {
463+
struct EditUserData: Content {
464+
static let defaultContentType = MediaType.urlEncodedForm
465+
let name = "Darth Vader"
466+
let username = "darth_vader"
467+
let twitterHandle = ""
468+
let profilePicture = ""
469+
let tagline = ""
470+
let biography = ""
471+
}
472+
473+
user.profilePicture = nil
474+
user.twitterHandle = nil
475+
user.tagline = nil
476+
user.biography = nil
477+
478+
let editData = EditUserData()
479+
_ = try testWorld.getResponse(to: "/admin/users/\(user.userID!)/edit", body: editData, loggedInUser: user)
480+
481+
XCTAssertEqual(testWorld.context.repository.users.count, 1)
482+
let updatedUser = try XCTUnwrap(testWorld.context.repository.users.last)
483+
XCTAssertEqual(updatedUser.username, editData.username)
484+
XCTAssertEqual(updatedUser.name, editData.name)
485+
XCTAssertNil(updatedUser.twitterHandle)
486+
XCTAssertNil(updatedUser.profilePicture)
487+
XCTAssertNil(updatedUser.tagline)
488+
XCTAssertNil(updatedUser.biography)
489+
XCTAssertEqual(updatedUser.userID, user.userID)
490+
}
491+
492+
func testUpdatingOptionalInfoToEmptyValuesWhenValueOriginallySetSetsItToNil() throws {
493+
struct EditUserData: Content {
494+
static let defaultContentType = MediaType.urlEncodedForm
495+
let name = "Darth Vader"
496+
let username = "darth_vader"
497+
let twitterHandle = ""
498+
let profilePicture = ""
499+
let tagline = ""
500+
let biography = ""
501+
}
502+
503+
user.profilePicture = "https://static.brokenhands.io/picture.png"
504+
user.tagline = "Tagline"
505+
user.biography = "Biography"
506+
user.twitterHandle = "darthVader"
507+
let editData = EditUserData()
508+
_ = try testWorld.getResponse(to: "/admin/users/\(user.userID!)/edit", body: editData, loggedInUser: user)
509+
510+
XCTAssertEqual(testWorld.context.repository.users.count, 1)
511+
let updatedUser = try XCTUnwrap(testWorld.context.repository.users.last)
512+
XCTAssertEqual(updatedUser.username, editData.username)
513+
XCTAssertEqual(updatedUser.name, editData.name)
514+
XCTAssertNil(updatedUser.twitterHandle)
515+
XCTAssertNil(updatedUser.profilePicture)
516+
XCTAssertNil(updatedUser.tagline)
517+
XCTAssertNil(updatedUser.biography)
518+
XCTAssertEqual(updatedUser.userID, user.userID)
519+
}
415520

416521
func testWhenEditingUserResetPasswordFlagSetIfRequired() throws {
417522
struct EditUserData: Content {
@@ -470,6 +575,27 @@ class AdminUserTests: XCTestCase {
470575
XCTAssertEqual(response.http.status, .seeOther)
471576
XCTAssertEqual(response.http.headers[.location].first, "/admin/")
472577
}
578+
579+
func testPasswordIsNotUpdatedWhenEmptyPasswordProvidedWhenEditingUser() throws {
580+
struct EditUserData: Content {
581+
static let defaultContentType = MediaType.urlEncodedForm
582+
let name = "Luke"
583+
let username = "lukes"
584+
let password = ""
585+
let confirmPassword = ""
586+
}
587+
588+
let oldPassword = user.password
589+
let editData = EditUserData()
590+
let response = try testWorld.getResponse(to: "/admin/users/\(user.userID!)/edit", body: editData, loggedInUser: user)
591+
592+
XCTAssertEqual(testWorld.context.repository.users.count, 1)
593+
let updatedUser = try XCTUnwrap(testWorld.context.repository.users.last)
594+
XCTAssertEqual(updatedUser.password, oldPassword)
595+
XCTAssertEqual(updatedUser.userID, user.userID)
596+
XCTAssertEqual(response.http.status, .seeOther)
597+
XCTAssertEqual(response.http.headers[.location].first, "/admin/")
598+
}
473599

474600
func testErrorShownWhenUpdatingUsersPasswordWithNonMatchingPasswords() throws {
475601
struct EditUserData: Content {
@@ -487,6 +613,7 @@ class AdminUserTests: XCTestCase {
487613
XCTAssertTrue(viewErrors.contains("Your passwords must match"))
488614
let passwordError = try XCTUnwrap(presenter.createUserPasswordError)
489615
let confirmPasswordError = try XCTUnwrap(presenter.createUserConfirmPasswordError)
616+
XCTAssertEqual(presenter.createUserUserID, user.userID)
490617
XCTAssertTrue(passwordError)
491618
XCTAssertTrue(confirmPasswordError)
492619
}
@@ -509,30 +636,6 @@ class AdminUserTests: XCTestCase {
509636
XCTAssertTrue(passwordError)
510637
}
511638

512-
func testErrorShownWhenTryingToChangeUsersPasswordWithEmptyString() throws {
513-
struct EditUserData: Content {
514-
static let defaultContentType = MediaType.urlEncodedForm
515-
let name = "Luke"
516-
let username = "lukes"
517-
let password = ""
518-
let confirmPassword = ""
519-
let resetPasswordOnLogin = true
520-
}
521-
522-
let editData = EditUserData()
523-
_ = try testWorld.getResponse(to: "/admin/users/\(user.userID!)/edit", body: editData, loggedInUser: user)
524-
525-
let viewErrors = try XCTUnwrap(presenter.createUserErrors)
526-
XCTAssertTrue(viewErrors.contains("You must confirm your password"))
527-
XCTAssertTrue(viewErrors.contains("You must specify a password"))
528-
let passwordError = try XCTUnwrap(presenter.createUserPasswordError)
529-
let confirmPasswordError = try XCTUnwrap(presenter.createUserConfirmPasswordError)
530-
XCTAssertTrue(passwordError)
531-
XCTAssertTrue(confirmPasswordError)
532-
let resetPasswordOnLogin = try XCTUnwrap(presenter.createUserResetPasswordRequired)
533-
XCTAssertTrue(resetPasswordOnLogin)
534-
}
535-
536639
func testPasswordIsActuallyHashedWhenEditingAUser() throws {
537640
testWorld = try! TestWorld.create(passwordHasherToUse: .reversed)
538641
let usersPassword = "password"

0 commit comments

Comments
 (0)