fix(license): call /v1 routes with product, name server refusals, localize purchase URL #359
3 changed files with 64 additions and 12 deletions
|
|
@ -469,14 +469,10 @@ async fn post_license_api(
|
|||
}
|
||||
}
|
||||
|
||||
/// Activation call, without `AppHandle` nor disk access so tests can point it at a mock
|
||||
/// server. Returns the activation token issued for `machine_id`.
|
||||
async fn activate_inner(
|
||||
api_base: &str,
|
||||
license_key: &str,
|
||||
machine_id: &str,
|
||||
machine_name: Option<&str>,
|
||||
) -> Result<String, LicenseError> {
|
||||
/// Body of an activation request. `machine_name` is omitted, never sent as `null`, when
|
||||
/// the host name is unreadable: the server's schema (`z.string().optional()`) answers
|
||||
/// `400 invalid_input` to a null.
|
||||
fn activate_body(license_key: &str, machine_id: &str, machine_name: Option<&str>) -> serde_json::Value {
|
||||
let mut body = serde_json::json!({
|
||||
"product": PRODUCT,
|
||||
"license_key": license_key.trim(),
|
||||
|
|
@ -485,6 +481,18 @@ async fn activate_inner(
|
|||
if let Some(name) = machine_name {
|
||||
body["machine_name"] = serde_json::Value::String(name.to_string());
|
||||
}
|
||||
body
|
||||
}
|
||||
|
||||
/// Activation call, without `AppHandle` nor disk access so tests can point it at a mock
|
||||
/// server. Returns the activation token issued for `machine_id`.
|
||||
async fn activate_inner(
|
||||
api_base: &str,
|
||||
license_key: &str,
|
||||
machine_id: &str,
|
||||
machine_name: Option<&str>,
|
||||
) -> Result<String, LicenseError> {
|
||||
let body = activate_body(license_key, machine_id, machine_name);
|
||||
let resp = post_license_api(api_base, "/v1/licenses/activate", &body).await?;
|
||||
resp["activation_token"]
|
||||
.as_str()
|
||||
|
|
@ -1115,6 +1123,35 @@ mod license_server_tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn activate_body_omits_an_unknown_machine_name() {
|
||||
let body = activate_body("SR-BASE-k", "machine-1", None);
|
||||
assert!(body.get("machine_name").is_none(), "body {}", body);
|
||||
assert_eq!(body["product"], "simpl-resultat");
|
||||
assert_eq!(
|
||||
activate_body("SR-BASE-k", "machine-1", Some("laptop"))["machine_name"],
|
||||
"laptop"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn deactivate_with_an_unreadable_success_body_is_an_invalid_response() {
|
||||
// A 2xx the client cannot read proves nothing: counting it as a success would
|
||||
// make the command delete this machine's local token for nothing.
|
||||
let mut server = mockito::Server::new_async().await;
|
||||
let _m = server
|
||||
.mock("POST", "/v1/licenses/deactivate")
|
||||
.with_status(200)
|
||||
.with_header("content-type", "text/html")
|
||||
.with_body("<html>OK</html>")
|
||||
.create_async()
|
||||
.await;
|
||||
assert_eq!(
|
||||
deactivate_inner(&server.url(), "SR-BASE-k", "machine-2").await,
|
||||
Err(LicenseError::InvalidResponse)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn activate_posts_the_trimmed_key_and_product_to_v1() {
|
||||
let mut server = mockito::Server::new_async().await;
|
||||
|
|
|
|||
|
|
@ -230,6 +230,20 @@ describe("selectCanActivate", () => {
|
|||
expect(selectCanActivate(reduce([{ type: "ACTIVATE_START" }]))).toBe(false);
|
||||
});
|
||||
|
||||
it("blocks the button while a deactivation is running", () => {
|
||||
// An activation racing the deactivation after a 409 could reach the server
|
||||
// first: the machine-limit message would vanish with 5 calls already spent.
|
||||
const s = reduce(
|
||||
[
|
||||
{ type: "LIST_LOAD_START" },
|
||||
{ type: "LIST_LOAD_DONE", machines: [machine("a"), machine("b")] },
|
||||
{ type: "DEACTIVATE_START", machineId: "a" },
|
||||
],
|
||||
limitHit,
|
||||
);
|
||||
expect(selectCanActivate(s)).toBe(false);
|
||||
});
|
||||
|
||||
it("blocks the button while the list loads", () => {
|
||||
// Otherwise a verify answer landing after a successful activation would
|
||||
// mark as fresh a list that no longer includes this machine.
|
||||
|
|
|
|||
|
|
@ -136,12 +136,13 @@ export function selectCanDeactivate(state: LicenseMachinesState): boolean {
|
|||
}
|
||||
|
||||
/**
|
||||
* The Activate button is live only when no activation and no list reload is running.
|
||||
* Same reason as for deactivation: a verify answer landing after a successful
|
||||
* activation would mark as fresh a list that no longer includes this machine.
|
||||
* The Activate button is live only when no activation, no list reload and no
|
||||
* deactivation is running. A verify answer landing after a successful activation would
|
||||
* mark as fresh a list that no longer includes this machine; an activation racing a
|
||||
* deactivation could clear the machine-limit message and spend the rate budget.
|
||||
*/
|
||||
export function selectCanActivate(state: LicenseMachinesState): boolean {
|
||||
return !state.activating && !state.listLoading;
|
||||
return !state.activating && !state.listLoading && state.deactivatingId === null;
|
||||
}
|
||||
|
||||
/** Whether opening the list (from `state`, before the toggle) should load it. */
|
||||
|
|
|
|||
Loading…
Reference in a new issue