module RemsimServer_Tests { /* Integration Tests for osmo-remsim-server * (C) 2019 by Harald Welte * All rights reserved. * * Released under the terms of GNU General Public License, Version 2 or * (at your option) any later version. * * SPDX-License-Identifier: GPL-2.0-or-later * * This test suite tests osmo-remsim-server by attaching to the external interfaces * such as RSPRO for simulated clients + bankds and RSRES (REST backend interface). */ import from Osmocom_Types all; import from RSPRO all; import from RSRES all; import from RSPRO_Types all; import from REMSIM_Tests all; import from Misc_Helpers all; import from IPA_Emulation all; import from HTTPmsg_Types all; import from HTTPmsg_PortType all; import from HTTP_Adapter all; import from JSON_Types all; /* run a HTTP GET on specified URL expecting json in RSRES format as response */ function f_rsres_get(charstring url, template integer exp_sts := 200) runs on http_CT return JsRoot { var HTTPMessage http_resp; http_resp := f_http_transact(url, exp := tr_HTTP_Resp(exp_sts)); return f_dec_JsRoot(char2oct(http_resp.response.body)); } /* run a HTTP PUT to add a new slotmap to the remsim-server */ function f_rsres_post_slotmap(JsSlotmap slotmap, template integer exp_sts := 201) runs on http_CT return HTTPResponse { var charstring body := oct2char(f_enc_JsSlotmap(slotmap)); var HTTPMessage http_resp; http_resp := f_http_transact(url := "/api/backend/v1/slotmaps", method := "POST", body := body, exp := tr_HTTP_Resp(exp_sts)) return http_resp.response; } /* run a HTTP PUT to add a new slotmap to the remsim-server */ function f_rsres_post_reset(template integer exp_sts := 200) runs on http_CT return HTTPResponse { var HTTPMessage http_resp; http_resp := f_http_transact(url := "/api/backend/v1/global-reset", method := "POST", body := "", exp := tr_HTTP_Resp(exp_sts)) return http_resp.response; } /* run a HTTP DELETE to remove a slotmap from te remsim-server */ function f_rsres_delete_slotmap(BankSlot bs, template integer exp_sts := 200) runs on http_CT return HTTPResponse { var HTTPMessage http_resp; var integer slotmap_id := bs.bankId * 65536 + bs.slotNr; http_resp := f_http_transact(url := "/api/backend/v1/slotmaps/" & int2str(slotmap_id), method := "DELETE", exp := tr_HTTP_Resp(exp_sts)); return http_resp.response; } function f_rsres_init() runs on http_CT { f_http_init(mp_server_ip, mp_rsres_port); f_rsres_post_reset(); } type component test_CT extends rspro_client_CT, http_CT { timer g_T_guard := 60.0; }; private altstep as_Tguard() runs on test_CT { [] g_T_guard.timeout { setverdict(fail, "Timeout of T_guard"); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } } private function f_init() runs on test_CT { /* Start the guard timer */ g_T_guard.start; activate(as_Tguard()); } testcase TC_connect_and_nothing() runs on test_CT { var ComponentIdentity rspro_id := valueof(ts_CompId(remsimClient, "foobar")); timer T := 20.0; f_init(); f_rspro_init(rspro[0], mp_server_ip, mp_server_port, rspro_id, 0); T.start; /* expect that we're disconnected if we never send a ConnectClientReq */ alt { [] RSPRO[0].receive(tr_ASP_IPA_EV(ASP_IPA_EVENT_ID_ACK)) { repeat; } [] RSPRO[0].receive(tr_ASP_IPA_EV(ASP_IPA_EVENT_DOWN)) { setverdict(pass); } [] T.timeout { setverdict(fail, "Timeout waiting for disconnect"); } } } testcase TC_connect_client() runs on test_CT { var ComponentIdentity rspro_id := valueof(ts_CompId(remsimClient, "foobar")); var JsRoot js; f_init(); f_rsres_init(); f_rspro_init(rspro[0], mp_server_ip, mp_server_port, rspro_id, 0); rspro[0].rspro_client_slot := valueof(ts_ClientSlot(3,4)); js := f_rsres_get("/api/backend/v1/clients"); if (not match(js.clients, JsClients:{})) { setverdict(fail, "Initial state not empty"); mtc.stop; } f_rspro_connect_client(0); js := f_rsres_get("/api/backend/v1/clients"); if (not match(js.clients[0], tr_JsClient(CONNECTED_CLIENT, rspro[0].rspro_id))) { setverdict(fail, "Non-matching JSON response"); mtc.stop; } //as_rspro_cfg_client_id(0, cslot); setverdict(pass); } /* duplicate connection from Same Client/Slot ID */ testcase TC_connect_client_duplicate() runs on test_CT { var ComponentIdentity rspro_id := valueof(ts_CompId(remsimClient, "foobar")); var JsRoot js; f_init(); f_rsres_init(); f_rspro_init(rspro[0], mp_server_ip, mp_server_port, rspro_id, 0); rspro[0].rspro_client_slot := valueof(ts_ClientSlot(13,1)); js := f_rsres_get("/api/backend/v1/clients"); if (not match(js.clients, JsClients:{})) { setverdict(fail, "Initial state not empty"); mtc.stop; } f_rspro_connect_client(0); js := f_rsres_get("/api/backend/v1/clients"); if (not match(js.clients[0], tr_JsClient(CONNECTED_CLIENT, rspro[0].rspro_id))) { setverdict(fail, "Non-matching JSON response"); mtc.stop; } /* connect a second time for same Client ID */ f_rspro_init(rspro[1], mp_server_ip, mp_server_port, rspro_id, 1); rspro[1].rspro_client_slot := valueof(ts_ClientSlot(13,1)); f_rspro_connect_client(1, identityInUse); f_rspro_exp_disconnect(1); /* expect the first connection still to be active */ js := f_rsres_get("/api/backend/v1/clients"); if (not match(js.clients[0], tr_JsClient(CONNECTED_CLIENT, rspro[0].rspro_id))) { setverdict(fail, "Non-matching JSON response"); mtc.stop; } setverdict(pass); } testcase TC_connect_bank() runs on test_CT { var ComponentIdentity rspro_id := valueof(ts_CompId(remsimBankd, "foobar")); var JsRoot js; f_init(); f_rsres_init(); f_rspro_init(rspro[0], mp_server_ip, mp_server_port, rspro_id, 0); rspro[0].rspro_bank_id := 1; rspro[0].rspro_bank_nslots := 8; js := f_rsres_get("/api/backend/v1/banks"); if (not match(js.banks, JsBanks:{})) { setverdict(fail, "Initial state not empty"); mtc.stop; } f_rspro_connect_client(0); js := f_rsres_get("/api/backend/v1/banks"); if (not match(js.banks[0], tr_JsBank(CONNECTED_BANKD, rspro[0].rspro_id, rspro[0].rspro_bank_id, rspro[0].rspro_bank_nslots))) { setverdict(fail, "Non-matching JSON response"); mtc.stop; } setverdict(pass); } testcase TC_connect_bank_duplicate() runs on test_CT { var ComponentIdentity rspro_id := valueof(ts_CompId(remsimBankd, "foobar")); var JsRoot js; f_init(); f_rsres_init(); f_rspro_init(rspro[0], mp_server_ip, mp_server_port, rspro_id, 0); rspro[0].rspro_bank_id := 1; rspro[0].rspro_bank_nslots := 8; js := f_rsres_get("/api/backend/v1/banks"); if (not match(js.banks, JsBanks:{})) { setverdict(fail, "Initial state not empty"); mtc.stop; } f_rspro_connect_client(0); js := f_rsres_get("/api/backend/v1/banks"); if (not match(js.banks[0], tr_JsBank(CONNECTED_BANKD, rspro[0].rspro_id, rspro[0].rspro_bank_id, rspro[0].rspro_bank_nslots))) { setverdict(fail, "Non-matching JSON response"); mtc.stop; } /* connect a second time for same Bank ID */ f_rspro_init(rspro[1], mp_server_ip, mp_server_port, rspro_id, 1); rspro[1].rspro_bank_id := 1; rspro[1].rspro_bank_nslots := 23; f_rspro_connect_client(1, identityInUse); /* expect only the first bank to survive */ js := f_rsres_get("/api/backend/v1/banks"); if (not match(js.banks[0], tr_JsBank(CONNECTED_BANKD, rspro[0].rspro_id, rspro[0].rspro_bank_id, rspro[0].rspro_bank_nslots))) { setverdict(fail, "Non-matching JSON response"); mtc.stop; } /* wait for T2/rejected timer to expire in server */ f_sleep(2.0); setverdict(pass); } function f_ensure_slotmaps(template JsSlotmaps maps) runs on http_CT { var JsRoot js; /* check that it is actually added */ js := f_rsres_get("/api/backend/v1/slotmaps"); if (match(js.slotmaps, maps)) { setverdict(pass); } else { setverdict(fail, "Unexpected slotmaps: ", js); } } /* verify that exactly only one slotmap exists (the specified one) */ function f_ensure_slotmap_exists_only(template ClientSlot cslot, template BankSlot bslot, template SlotmapState state := ?) runs on http_CT { f_ensure_slotmaps({ tr_JsSlotmap(bslot, cslot, state) } ); } /* verify that exactly only one slotmap exists (possibly among others) */ function f_ensure_slotmap_exists(template ClientSlot cslot, template BankSlot bslot, template SlotmapState state := ?) runs on http_CT { f_ensure_slotmaps({ *, tr_JsSlotmap(bslot, cslot, state), * } ); } /* test adding a single slotmap */ testcase TC_slotmap_add() runs on test_CT { f_init(); f_rsres_init(); var JsSlotmap sm := valueof(ts_JsSlotmap(ts_BankSlot(1,2), ts_ClientSlot(3,4))); var HTTPResponse res := f_rsres_post_slotmap(sm); /* check that it is actually added */ f_ensure_slotmap_exists_only(sm.client, sm.bank, NEW); } /* test adding a single slotmap with out-of-range values */ testcase TC_slotmap_add_out_of_range() runs on test_CT { f_init(); f_rsres_init(); var HTTPMessage http_resp; var charstring body; body := "{ \"bank\": { \"bankId\": 10000, \"slotNr\": 2 }, \"client\": { \"clientId\": 3, \"slotNr\": 4 } }"; http_resp := f_http_transact(url := "/api/backend/v1/slotmaps", method := "POST", body := body, exp := tr_HTTP_Resp(400)); body := "{ \"bank\": { \"bankId\": 100, \"slotNr\": 2000 }, \"client\": { \"clientId\": 3, \"slotNr\": 4 } }"; http_resp := f_http_transact(url := "/api/backend/v1/slotmaps", method := "POST", body := body, exp := tr_HTTP_Resp(400)); body := "{ \"bank\": { \"bankId\": 100, \"slotNr\": 2 }, \"client\": { \"clientId\": 3000, \"slotNr\": 4 } }"; http_resp := f_http_transact(url := "/api/backend/v1/slotmaps", method := "POST", body := body, exp := tr_HTTP_Resp(400)); body := "{ \"bank\": { \"bankId\": 100, \"slotNr\": 2 }, \"client\": { \"clientId\": 3, \"slotNr\": 4000 } }"; http_resp := f_http_transact(url := "/api/backend/v1/slotmaps", method := "POST", body := body, exp := tr_HTTP_Resp(400)); } /* test adding a slotmap and then connecting a client + bankd */ testcase TC_slotmap_add_conn_cl_b() runs on test_CT { f_init(); /* Simulate one client */ var ComponentIdentity rspro_id := valueof(ts_CompId(remsimClient, testcasename())); f_rspro_init(rspro[0], mp_server_ip, mp_server_port, rspro_id, 0); rspro[0].rspro_client_slot := valueof(ts_ClientSlot(3,4)); /* Simulate one bankd */ var BankSlot bslot := valueof(ts_BankSlot(1,2)); var ComponentIdentity rspro_bank_id := valueof(ts_CompId(remsimBankd, testcasename())); f_rspro_init(rspro[1], mp_server_ip, mp_server_port, rspro_bank_id, 1); rspro[1].rspro_bank_id := bslot.bankId; rspro[1].rspro_bank_nslots := 8 f_rsres_init(); var JsSlotmap sm := valueof(ts_JsSlotmap(bslot, rspro[0].rspro_client_slot)); var HTTPResponse res; /* 1) Create a new slotmap via HTTP */ res := f_rsres_post_slotmap(sm); /* 2) verify that the slotmap exists and is NEW */ f_ensure_slotmap_exists_only(sm.client, sm.bank, NEW); /* 3) connect a client for that slotmap */ f_rspro_connect_client(0); /* 4) connect a bankd for that slotmap */ f_rspro_connect_client(1); /* 5) verify that the slotmap exists and is UNACKNOWLEDGED */ f_ensure_slotmap_exists_only(sm.client, sm.bank, UNACKNOWLEDGED); /* 6) expect bankd to receive that mapping */ as_rspro_create_mapping(1, sm.client, sm.bank); /* 7) verify that the slotmap exists and is ACTIVE */ f_ensure_slotmap_exists_only(sm.client, sm.bank, ACTIVE); /* 8) expect the client to be configured with bankd side settings */ as_rspro_cfg_client_bank(0, bslot, ?); } /* test connecting a client and later adding a slotmap for it */ testcase TC_conn_cl_b_slotmap_add() runs on test_CT { f_init(); /* Simulate one client */ var ComponentIdentity rspro_id := valueof(ts_CompId(remsimClient, testcasename())); f_rspro_init(rspro[0], mp_server_ip, mp_server_port, rspro_id, 0); rspro[0].rspro_client_slot := valueof(ts_ClientSlot(3,4)); /* Simulate one bankd */ var BankSlot bslot := valueof(ts_BankSlot(1,2)); var ComponentIdentity rspro_bank_id := valueof(ts_CompId(remsimBankd, testcasename())); f_rspro_init(rspro[1], mp_server_ip, mp_server_port, rspro_bank_id, 1); rspro[1].rspro_bank_id := bslot.bankId; rspro[1].rspro_bank_nslots := 8 f_rsres_init(); var JsSlotmap sm := valueof(ts_JsSlotmap(bslot, rspro[0].rspro_client_slot)); var HTTPResponse res; /* 1) connect a client for that slotmap */ f_rspro_connect_client(0); /* 2) Create a new slotmap via HTTP */ res := f_rsres_post_slotmap(sm); /* 3) verify that the slotmap exists and is NEW */ f_ensure_slotmap_exists_only(sm.client, sm.bank, NEW); /* 4) connect a bankd for that slotmap */ f_rspro_connect_client(1); /* 5) verify that the slotmap exists and is UNACKNOWLEDGED */ f_ensure_slotmap_exists_only(sm.client, sm.bank, UNACKNOWLEDGED); /* 6) expect bankd to receive that mapping */ as_rspro_create_mapping(1, sm.client, sm.bank); /* 7) verify that the slotmap exists and is ACTIVE */ f_ensure_slotmap_exists_only(sm.client, sm.bank, ACTIVE); /* 8) expect the client to be configured with bankd IP/port */ as_rspro_cfg_client_bank(0, bslot, ?); } /* simple delete of a 'NEW' slotmap */ testcase TC_slotmap_del_new() runs on test_CT { f_init(); f_rsres_init(); var JsSlotmap sm := valueof(ts_JsSlotmap(ts_BankSlot(1,2), ts_ClientSlot(3,4))); var HTTPResponse res := f_rsres_post_slotmap(sm); log(res); res := f_rsres_delete_slotmap(sm.bank); log(res); } /* simple delete of a non-existant slotmap */ testcase TC_slotmap_del_nonexistant() runs on test_CT { f_init(); f_rsres_init(); var JsSlotmap sm := valueof(ts_JsSlotmap(ts_BankSlot(11,12), ts_ClientSlot(13,14))); var HTTPResponse res := f_rsres_delete_slotmap(sm.bank, exp_sts:=404); log(res); } /* simple delete of a 'UNACKNOWLEDGED' slotmap */ testcase TC_slotmap_del_unack() runs on test_CT { var ComponentIdentity rspro_id := valueof(ts_CompId(remsimBankd, testcasename())); f_init(); f_rspro_init(rspro[0], mp_server_ip, mp_server_port, rspro_id, 0); rspro[0].rspro_bank_id := 1; rspro[0].rspro_bank_nslots := 8; f_rsres_init(); var JsSlotmap sm := valueof(ts_JsSlotmap(ts_BankSlot(1,2), ts_ClientSlot(3,4))); var HTTPResponse res; /* Create a new slotmap via HTTP */ res := f_rsres_post_slotmap(sm); /* verify that the slotmap exists and is NEW */ f_ensure_slotmap_exists_only(sm.client, sm.bank, NEW); /* connect a bankd for that slotmap */ f_rspro_connect_client(0); /* expect the slotmap to be pushed to bank but don't ACK it */ f_rspro_exp(tr_RSPRO_CreateMappingReq(sm.client, sm.bank)); /* verify that the slotmap exists and is UNACKNOWLEDGED */ f_ensure_slotmap_exists_only(sm.client, sm.bank, UNACKNOWLEDGED); /* delete the slotmap via REST */ res := f_rsres_delete_slotmap(sm.bank); /* verify the slotmap is gone */ f_ensure_slotmaps({}); } /* simple delete of a 'ACTIVE' slotmap from server + bankd */ testcase TC_slotmap_del_active() runs on test_CT { var ComponentIdentity rspro_id := valueof(ts_CompId(remsimBankd, testcasename())); f_init(); f_rspro_init(rspro[0], mp_server_ip, mp_server_port, rspro_id, 0); rspro[0].rspro_bank_id := 1; rspro[0].rspro_bank_nslots := 8; f_rsres_init(); var JsSlotmap sm := valueof(ts_JsSlotmap(ts_BankSlot(1,2), ts_ClientSlot(3,4))); var HTTPResponse res; /* Create a new slotmap via HTTP */ res := f_rsres_post_slotmap(sm); /* verify that the slotmap exists and is NEW */ f_ensure_slotmap_exists_only(sm.client, sm.bank, NEW); /* connect a bankd for that slotmap */ f_rspro_connect_client(0); /* expect the slotmap to be pushed to bank and ACK it */ as_rspro_create_mapping(0, sm.client, sm.bank); /* verify that the slotmap exists and is ACTIVE */ f_ensure_slotmap_exists_only(sm.client, sm.bank, ACTIVE); f_sleep(1.0); /* delete the slotmap via REST */ res := f_rsres_delete_slotmap(sm.bank); /* verify the slotmap is gone from REST interface immediately */ f_ensure_slotmaps({}); /* verify the slotmap is removed from bankd */ as_rspro_remove_mapping(0, sm.client, sm.bank); } /* simple delete of a 'ACTIVE' slotmap from client */ testcase TC_slotmap_del_active_client() runs on test_CT { var ComponentIdentity rspro_id := valueof(ts_CompId(remsimBankd, testcasename())); f_init(); f_rspro_init(rspro[0], mp_server_ip, mp_server_port, rspro_id, 0); rspro[0].rspro_bank_id := 1; rspro[0].rspro_bank_nslots := 8; rspro_id := valueof(ts_CompId(remsimClient, testcasename())); f_rspro_init(rspro[1], mp_server_ip, mp_server_port, rspro_id, 1); rspro[1].rspro_client_slot := valueof(ts_ClientSlot(3,4)); f_rsres_init(); var JsSlotmap sm := valueof(ts_JsSlotmap(ts_BankSlot(1,2), ts_ClientSlot(3,4))); var HTTPResponse res; /* Create a new slotmap via HTTP */ res := f_rsres_post_slotmap(sm); /* verify that the slotmap exists and is NEW */ f_ensure_slotmap_exists_only(sm.client, sm.bank, NEW); /* connect a bankd for that slotmap */ f_rspro_connect_client(0); /* connect a client for that slotmap */ f_rspro_connect_client(1); /* expect the slotmap to be pushed to bank and ACK it */ as_rspro_create_mapping(0, sm.client, sm.bank); /* verify that the slotmap exists and is ACTIVE */ f_ensure_slotmap_exists_only(sm.client, sm.bank, ACTIVE); /* expect the client to be configured with bankd side settings */ as_rspro_cfg_client_bank(1, sm.bank, ?/*FIXME*/); f_sleep(1.0); /* delete the slotmap via REST */ res := f_rsres_delete_slotmap(sm.bank); /* verify the slotmap is gone from REST interface immediately */ f_ensure_slotmaps({}); /* verify the slotmap is removed from bankd */ as_rspro_remove_mapping(0, sm.client, sm.bank); /* verify the slotmap is removed from client by setting IP/port to '0' */ as_rspro_cfg_client_bank(1, ?, tr_IpPort(ts_IPv4("0.0.0.0"), 0)); } /* Add a slotmap to a currently active bank */ testcase TC_slotmap_add_active_bank() runs on test_CT { var ComponentIdentity rspro_id := valueof(ts_CompId(remsimBankd, testcasename())); f_init(); f_rspro_init(rspro[0], mp_server_ip, mp_server_port, rspro_id, 0); rspro[0].rspro_bank_id := 1; rspro[0].rspro_bank_nslots := 8; f_rsres_init(); var JsSlotmap sm := valueof(ts_JsSlotmap(ts_BankSlot(1,2), ts_ClientSlot(3,4))); var HTTPResponse res; /* connect a bankd for that slotmap */ f_rspro_connect_client(0); /* Create a new slotmap via HTTP */ res := f_rsres_post_slotmap(sm); /* expect the slotmap to be pushed to bank and ACK it */ as_rspro_create_mapping(0, sm.client, sm.bank); /* verify that the slotmap exists and is ACTIVE */ f_ensure_slotmap_exists_only(sm.client, sm.bank, ACTIVE); } /* TODO * - connect client w/slotmap; delete slotmap via REST (see if it is deleted) * - don't acknowledge delete from client, disconnect client (see if slotmap is deleted) * - connect from unknown client (name not known, no clientId provisioned? * - add client name/ID mappings from REST API? */ control { execute( TC_connect_and_nothing() ); execute( TC_connect_client() ); execute( TC_connect_client_duplicate() ); execute( TC_connect_bank() ); execute( TC_connect_bank_duplicate() ); execute( TC_slotmap_add() ); execute( TC_slotmap_add_conn_cl_b() ); execute( TC_slotmap_add_out_of_range() ); execute( TC_conn_cl_b_slotmap_add() ); execute( TC_slotmap_del_new() ); execute( TC_slotmap_del_nonexistant() ); execute( TC_slotmap_del_unack() ); execute( TC_slotmap_del_active() ); execute( TC_slotmap_del_active_client() ); execute( TC_slotmap_add_active_bank() ); } }