| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 |
- using Invercargill;
- using Invercargill.DataStructures;
- using Invercargill.Mapping;
- using InvercargillJson;
- using Inversion;
- using Statum;
- namespace Statum.Tests {
- int failures = 0;
- int passes = 0;
- void check(bool condition, string label) {
- if (condition) {
- passes++;
- print("PASS %s\n", label);
- } else {
- failures++;
- print("FAIL %s\n", label);
- }
- }
- /**
- * Scalar model in the style of Percipio's login private data: the field
- * types that previously crashed (int64, bool) or mis-serialised (int) the
- * encryption path.
- */
- public class LoginPrivate : Object {
- public int64 user_id { get; set; }
- public bool is_admin { get; set; }
- public int attempts { get; set; }
- public double score { get; set; }
- public string username { get; set; }
- public long session_no { get; set; }
- }
- /** List-bearing model: a `string[]` plus a collection of a registered type. */
- public class ArrayModel : Object {
- public string[] tags { get; set; }
- public Series<Model.ActionDto> refs { get; set; }
- }
- /** Typed action WITHOUT a `private_type` override — demonstrates S-8 erosion. */
- public class ErodingAction : TypedStatumAction<LoginPrivate> {
- public Type exposed_private_type() {
- return private_type;
- }
- public LoginPrivate? exposed_request_private() {
- return request_private;
- }
- public void feed(Properties props) {
- request = new StatumRequest() { action_private = props };
- }
- public override async DirectiveBuilder handle() throws GLib.Error {
- return directives();
- }
- }
- /** Typed action WITH the `private_type` override — the supported pattern. */
- public class FixedAction : TypedStatumAction<LoginPrivate> {
- protected override Type private_type { get { return typeof(LoginPrivate); } }
- public Type exposed_private_type() {
- return private_type;
- }
- public LoginPrivate? exposed_request_private() {
- return request_private;
- }
- public void feed(Properties props) {
- request = new StatumRequest() { action_private = props };
- }
- public override async DirectiveBuilder handle() throws GLib.Error {
- return directives();
- }
- }
- LoginPrivate sample_private() {
- return new LoginPrivate() {
- user_id = (int64)9007199254740993,
- is_admin = true,
- attempts = 3,
- score = 0.5,
- username = "bbarrow",
- session_no = (long)123456789012345
- };
- }
- void assert_private_matches(LoginPrivate expected, LoginPrivate actual, string label) {
- check(actual.user_id == expected.user_id, label + ": user_id int64 round-trip");
- check(actual.is_admin == expected.is_admin, label + ": is_admin bool round-trip");
- check(actual.attempts == expected.attempts, label + ": attempts int round-trip");
- check(actual.score == expected.score, label + ": score double round-trip");
- check(actual.username == expected.username, label + ": username string round-trip");
- check(actual.session_no == expected.session_no, label + ": session_no long round-trip");
- }
- void test_scalar_public_path() throws GLib.Error {
- var model = sample_private();
- var props = GObjectMapping.to_properties(model);
- var wire = new JsonElement.from_properties(props).stringify();
- check(wire.contains("\"user_id\":9007199254740993"), "public path: int64 serialises as an unquoted JSON number");
- check(wire.contains("\"is_admin\":true"), "public path: bool serialises as an unquoted JSON boolean");
- check(wire.contains("\"attempts\":3"), "public path: int serialises as an unquoted JSON number, not a string");
- check(wire.contains("\"score\":0.5"), "public path: double serialises as an unquoted JSON number");
- var back = (LoginPrivate) GObjectMapping.from_properties(typeof(LoginPrivate),
- new JsonElement.from_string(wire).as<Properties>());
- assert_private_matches(model, back, "public path");
- }
- void test_scalar_encryption_path(Statum.Cryptography.EncryptionProvider encryption) throws GLib.Error {
- var model = sample_private();
- var blob = encryption.author_properties("statum-tests", GObjectMapping.to_properties(model));
- var props = encryption.read_properties("statum-tests", blob);
- var back = (LoginPrivate) GObjectMapping.from_properties(typeof(LoginPrivate), props);
- assert_private_matches(model, back, "encryption path");
- }
- void test_arrays(Statum.Cryptography.EncryptionProvider encryption) throws GLib.Error {
- var first = new Model.ActionDto() { uri = "/_statum/action/aaaa", method = "POST" };
- var second = new Model.ActionDto() { uri = "/_statum/action/bbbb", method = "DELETE" };
- var refs = new Series<Model.ActionDto>();
- refs.add(first);
- refs.add(second);
- var model = new ArrayModel() { tags = new string[] { "alpha", "beta", "gamma" }, refs = refs };
- var props = GObjectMapping.to_properties(model);
- var wire = new JsonElement.from_properties(props).stringify();
- check(wire.contains("\"tags\":[\"alpha\",\"beta\",\"gamma\"]"), "arrays: string[] serialises as a JSON array");
- check(wire.contains("\"uri\":\"/_statum/action/bbbb\"")
- && wire.contains("\"method\":\"DELETE\""), "arrays: registered-Object collection serialises as an array of nested objects");
- var parsed = new JsonElement.from_string(wire).as<Properties>();
- var array_model = (ArrayModel) GObjectMapping.from_properties(typeof(ArrayModel), parsed);
- check(array_model.tags.length == 3 && array_model.tags[0] == "alpha"
- && array_model.tags[1] == "beta" && array_model.tags[2] == "gamma", "arrays: string[] read-back from JSON array");
- var blob = encryption.author_properties("statum-tests", props);
- var decrypted = (ArrayModel) GObjectMapping.from_properties(typeof(ArrayModel),
- encryption.read_properties("statum-tests", blob));
- check(decrypted.tags.length == 3 && decrypted.tags[2] == "gamma", "arrays: string[] round-trips through the encryption path");
- }
- void test_typed_action_private(Inversion.Scope scope, Statum.Cryptography.EncryptionProvider encryption) throws GLib.Error {
- var eroding = (ErodingAction) scope.resolve_type(typeof(ErodingAction));
- check(eroding.exposed_private_type() == typeof(void), "S-8 repro: typeof(TPrivate) erodes to void for a reflectively-instantiated action");
- eroding.feed(new PropertyDictionary());
- check(eroding.exposed_request_private() == null, "S-8 repro: un-overridden request_private yields null without crashing");
- var fixed_action = (FixedAction) scope.resolve_type(typeof(FixedAction));
- check(fixed_action.exposed_private_type() == typeof(LoginPrivate), "S-8 fix: private_type override captures the GType from a foreign namespace");
- var model = sample_private();
- var blob = encryption.author_properties(fixed_action.get_type().name(), GObjectMapping.to_properties(model));
- fixed_action.feed(encryption.read_properties(fixed_action.get_type().name(), blob));
- var private_data = fixed_action.exposed_request_private();
- check(private_data != null, "S-8 fix: request_private materialises from a sealed blob");
- if (private_data != null) {
- assert_private_matches(model, (!)private_data, "S-8 fix");
- }
- }
- void main() {
- GObjectMapping.register_mapper<Model.ActionDto>(Model.ActionDto.get_mapper());
- var container = new Container();
- container.register_singleton<Statum.Cryptography.SigningProvider>();
- container.register_singleton<Statum.Cryptography.EncryptionProvider>();
- container.register_singleton<StateService>();
- container.register_singleton<HeldSlotResolver>();
- container.register_singleton<ChannelEndpoint>().as<ChannelService>();
- container.register_singleton<ActionRegistry>();
- container.register_singleton<TopicRegistry>();
- container.register_scoped<ErodingAction>();
- container.register_scoped<FixedAction>();
- var scope = container.create_transient_scope();
- var encryption = (Statum.Cryptography.EncryptionProvider) scope.resolve_type(typeof(Statum.Cryptography.EncryptionProvider));
- try {
- test_scalar_public_path();
- } catch (GLib.Error e) {
- failures++;
- print("FAIL scalar public path threw: %s\n", e.message);
- }
- try {
- test_scalar_encryption_path(encryption);
- } catch (GLib.Error e) {
- failures++;
- print("FAIL scalar encryption path threw: %s\n", e.message);
- }
- try {
- test_arrays(encryption);
- } catch (GLib.Error e) {
- failures++;
- print("FAIL arrays threw: %s\n", e.message);
- }
- try {
- test_typed_action_private(scope, encryption);
- } catch (GLib.Error e) {
- failures++;
- print("FAIL typed action private threw: %s\n", e.message);
- }
- print("---- %d passed, %d failed ----\n", passes, failures);
- if (failures > 0) {
- Process.exit(1);
- }
- }
- }
|