Run unit tests
After making the input verification changes to the queries, running the tests will show that they fail with the message
User must sign this operation.
This is a good sign because the original test implementation didn't consider signing.
We need to add .sign(keypair)
to the test transactions to fix this. For example, in test_create_entities
, we sign
the transaction with Alice's keypair:
val alice_kp = rell.test.keypairs.alice; // <--
function test_create_entities() {
rell.test.tx()
.op(create_user("Alice", alice))
.op(create_user("Bob", bob))
.run();
assert_equals(user @ { } (@sum 1), 2);
rell.test.tx()
.op(follow_user(alice, bob))
.op(make_post(alice, "My post"))
.sign(alice_kp) // <--
.run();
assert_true(is_following(alice, bob));
assert_equals(follower @ { } (@sum 1), 1);
assert_equals(post @ { } (@sum 1), 1);
rell.test.tx()
.op(unfollow_user(alice, bob))
.sign(alice_kp) // <--
.run();
assert_false(is_following(alice, bob));
assert_equals(follower @ { } (@sum 1), 0);
}
Similarly, for the second test case test_follower_calculation
:
function test_follower_calculation() {
...
rell.test.tx()
.op(follow_user(alice, bob))
.op(follow_user(alice, charlie))
.sign(alice_kp) // <--
.run();
...
}
For the last test case, we need Alice
to sign the follow_user
transaction and Bob
to sign the make_post
transactions. We add another constant for Bob's keypair and make the following adjustment:
val bob_kp = rell.test.keypairs.bob;
function test_pagination_of_posts() {
rell.test.tx()
.op(create_user("Alice", alice))
.op(create_user("Bob", bob)).run();
rell.test.tx()
.op(follow_user(alice, bob))
.sign(alice_kp) // <--
.run();
for (i in range(5)) {
rell.test.tx().op(make_post(bob, "Content %d".format(i))).sign(bob_kp).run(); // <--
}
...
}
Now, all the tests are working again.
Additional testing
Let's add a new test case to ensure that impersonation can't happen and that the input validation works:
val charlie_kp = rell.test.keypairs.charlie;
function test_input_verification() {
rell.test.tx()
.op(create_user("Alice", alice))
.op(create_user("Bob", bob)).run();
// Bob cannot impersonate Alice
rell.test.tx()
.op(follow_user(alice, bob))
.sign(bob_kp)
.run_must_fail();
rell.test.tx()
.op(make_post(alice, "My malicious post"))
.sign(bob_kp)
.run_must_fail();
// Alice cannot follow non-existing Charlie
val f1 = rell.test.tx()
.op(follow_user(alice, charlie))
.sign(alice_kp)
.run_must_fail();
assert_true(f1.message.contains("does not exist"));
// Charlie cannot create a post since he does not exist
val f2 = rell.test.tx()
.op(make_post(charlie, "My secret post"))
.sign(charlie_kp)
.run_must_fail();
assert_true(f2.message.contains("does not exist"));
}
Manual testing
To test this manually, like in the previous module, we must store the generated keypairs, not just the public keys. We can do this by
chr keygen --file <target>
Chromia CLI looks for .chromia/config
, but it's possible to override this using the --secret
flag of
chr tx. We can save the keypairs for Alice and Bob as follows:
chr keygen --file .chromia/config-alice
chr keygen --file .chromia/config-bob
You can use these keypairs when making transactions, ensuring you sign with the correct key.
chr node start
chr tx --await create_user Alice 'x"<alice-pubkey>"'
chr tx --await create_user Bob 'x"<bob-pubkey>"'
chr tx --await --secret .chromia/config-bob follow_user 'x"<alice-pubkey>"' 'x"<bob-pubkey>"' # Fails
chr tx --await --secret .chromia/config-alice follow_user 'x"<alice-pubkey>"' 'x"<bob-pubkey>"' # Succeeds
Congratulations! You've learned how to perform basic input validation on queries and operations. In the next module, we'll explore how to manage accounts more structured and securely using the ft-library.