I'm using BCrypt to encrypt passwords, and I have a model that I'm trying to write tests for. I have made a YAML file with some basic test data in it, but I can't seem to get the code right.
My Account
model:
package models;
import javax.persistence.*;
import org.mindrot.jbcrypt.BCrypt;
import play.data.validation.Constraints.Required;
import play.db.ebean.Model;
import play.db.ebean.Model.Finder;
@Entity
public class Account extends Model {
@Id
public Long id;
public String email;
public String username;
public String passwordhash;
public Account(String email, String username, String password) {
this.email = email;
this.username = username;
this.passwordhash = BCrypt.hashpw(password, BCrypt.gensalt());
}
public static Finder<Long, Account> find = new Finder<Long, Account>(Long.class, Account.class);
public static Account create(
String username, String email, String password,
String confirmPassword
) {
Account account = new Account(username, email, password);
account.save();
return account;
}
public static Account authenticate (String username, String password) {
Account account = Account.find.where().eq("username", username).findUnique();
if (account != null && BCrypt.checkpw(password, account.passwordhash)) {
return account;
} else {
return null;
}
}
}
My Model Test:
package ModelTests;
import models.*;
import org.junit.*;
import com.avaje.ebean.Ebean;
import static org.junit.Assert.*;
import play.libs.Yaml;
import play.test.WithApplication;
import static play.test.Helpers.*;
import java.util.List;
public class AccountTest extends WithApplication {
@Before
public void setUp() {
start(fakeApplication(inMemoryDatabase()));
Ebean.save((List) Yaml.load("test-data.yml"));
}
@Test
public void authentication() {
assertNotNull(Account.authenticate("palug", "Oxford4EVA"));
assertNotNull(Account.authenticate("isthatastrawhat", "Supernatural"));
assertNull(Account.authenticate("MelvIsntNormal", "meaplesss"));
assertNull(Account.authenticate("SomeStupidHacker", "dogewow"));
}
}
And my test data YAML:
# Accounts
- &melvin !!models.Account
email: mxtcapps@maxtrace.co.uk
username: MelvIsntNormal
password: HashedPassword
- &emma !!models.Account
email: isthatastrawhat@maxtrace.co.uk
username: isthatastrawhat
password: Supernatural
- &paul !!models.Account
email: palug@maxtrace.co.uk
username: palug
password: Oxford4EVA
When I run the test, I get this error:
[error] Test ModelTests.AccountTest.authentication failed: org.yaml.snakeyaml.constructor.ConstructorException: null; Can't construct a java object for tag:yaml.org,2002:models.Account; exception=Cannot create property=password for JavaBean=models.Account@1c8d2d9; Unable to find property 'password' on class: models.Account; in 'reader', line 3, column 3:
[error] - &melvin !!models.Account
[error] ^
[error] at org.yaml.snakeyaml.constructor.Constructor$ConstructYamlObject.construct(Constructor.java:333)
[error] at org.yaml.snakeyaml.constructor.BaseConstructor.constructObject(BaseConstructor.java:182)
[error] at org.yaml.snakeyaml.constructor.BaseConstructor.constructSequenceStep2(BaseConstructor.java:276)
[error] at org.yaml.snakeyaml.constructor.BaseConstructor.constructSequence(BaseConstructor.java:247)
[error] at org.yaml.snakeyaml.constructor.SafeConstructor$ConstructYamlSeq.construct(SafeConstructor.java:442)
[error] at org.yaml.snakeyaml.constructor.BaseConstructor.constructObject(BaseConstructor.java:182)
[error] at org.yaml.snakeyaml.constructor.BaseConstructor.constructDocument(BaseConstructor.java:141)
[error] at org.yaml.snakeyaml.constructor.BaseConstructor.getSingleData(BaseConstructor.java:127)
[error] at org.yaml.snakeyaml.Yaml.loadFromReader(Yaml.java:481)
[error] at org.yaml.snakeyaml.Yaml.load(Yaml.java:412)
[error] at play.libs.Yaml.load(Yaml.java:31)
[error] at play.libs.Yaml.load(Yaml.java:18)
[error] at ModelTests.AccountTest.setUp(AccountTest.java:18)
[error] ...
[error] Caused by: org.yaml.snakeyaml.error.YAMLException: Cannot create property=password for JavaBean=models.Account@1c8d2d9; Unable to find property 'password' on class: models.Account
[error] at org.yaml.snakeyaml.constructor.Constructor$ConstructMapping.constructJavaBean2ndStep(Constructor.java:299)
[error] at org.yaml.snakeyaml.constructor.Constructor$ConstructMapping.construct(Constructor.java:189)
[error] at org.yaml.snakeyaml.constructor.Constructor$ConstructYamlObject.construct(Constructor.java:331)
[error] ... 48 more
[error] Caused by: org.yaml.snakeyaml.error.YAMLException: Unable to find property 'password' on class: models.Account
[error] at org.yaml.snakeyaml.introspector.PropertyUtils.getProperty(PropertyUtils.java:132)
[error] at org.yaml.snakeyaml.introspector.PropertyUtils.getProperty(PropertyUtils.java:121)
[error] at org.yaml.snakeyaml.constructor.Constructor$ConstructMapping.getProperty(Constructor.java:308)
[error] at org.yaml.snakeyaml.constructor.Constructor$ConstructMapping.constructJavaBean2ndStep(Constructor.java:240)
[error] ... 50 more
[info] ModelTests.AccountTest
[info] x authentication
[info]
[info]
[info] Total for test ModelTests.AccountTest
[info] Finished in 0.039 seconds
[info] 1 tests, 1 failures, 0 errors
[error] Failed: Total 1, Failed 1, Errors 0, Passed 0
[error] Failed tests:
[error] ModelTests.AccountTest
[error] (test:test) sbt.TestsFailedException: Tests unsuccessful
[error] Total time: 26 s, completed 01-Feb-2014 07:59:02
I understand the error is caused by the fact the the password field does not exist. However, I'm not quite sure how to go about getting the tests to invoke the Account method so the passwords would be encrypted. Can anyone help with this?
QUESTION ANSWER (Thanks @Joe)
I had to add a setter method for the password field:
public void setPassword(String password) {
this.passwordhash = BCrypt.hashpw(password, BCrypt.gensalt());
}
My Account
model now looks like this:
package models;
import javax.persistence.*;
import org.mindrot.jbcrypt.BCrypt;
import play.data.validation.Constraints.Required;
import play.db.ebean.Model;
import play.db.ebean.Model.Finder;
@Entity
public class Account extends Model {
@Id
public Long id;
public String email;
public String username;
public String passwordhash;
public Account(String email, String username, String password) {
this.email = email;
this.username = username;
this.passwordhash = BCrypt.hashpw(password, BCrypt.gensalt());
}
public static Finder<Long, Account> find = new Finder<Long, Account>(Long.class, Account.class);
public static Account create(
String username, String email, String password,
String confirmPassword
) {
Account account = new Account(username, email, password);
account.save();
return account;
}
public void setPassword(String password) {
this.passwordhash = BCrypt.hashpw(password, BCrypt.gensalt());
}
public static Account authenticate (String username, String password) {
Account account = Account.find.where().eq("username", username).findUnique();
if (account != null && BCrypt.checkpw(password, account.passwordhash)) {
return account;
} else {
return null;
}
}
}