Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .config/nextest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,16 @@ app = { max-threads = 1 }
# tiberius 0.13 stops reading at the first end of message, so an attention
# sent while SQL Server is still waiting is never acknowledged and the
# connection is dropped: https://github.com/tiberius-rs/tiberius/issues/459.
# Remove once a release has the fix.
# Remove once a release has the fix. The tests against a tablepro-cloud
# stack run by hand (linux/docs/testing.md, tablepro-cloud stack): CI
# cannot reach the private backend.
default-filter = """
not (package(tablepro-driver-mssql) & (
test(=cancellation::editor_waitfor_cancel_then_select1_same_client)
| test(=cancellation::pooled_query_bound_waitfor_deadline_server_cancelled_client_reused)
| test(=conformance_engine::suite::editor_cancel_reports_cancelled_and_session_stays_ready)
))
& not (test(/::cloud_stack::/) | binary(cloud_stack))
"""

[[profile.default.overrides]]
Expand Down
6 changes: 4 additions & 2 deletions linux/crates/app/src/ui/connect_dialog/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1569,6 +1569,7 @@ fn the_socket_switch_trades_the_host_for_the_socket_and_back() {
let harness = dialog();
select(&harness.dialog, "mysql");
fill_network(&harness.dialog);
select_mode(&harness.dialog, TlsMode::Require);
let imp = harness.dialog.imp();
assert!(imp.socket_row.is_visible());
assert!(imp.tls_section.is_visible() && imp.ssh_section.is_visible());
Expand All @@ -1593,7 +1594,7 @@ fn the_socket_switch_trades_the_host_for_the_socket_and_back() {
assert!(imp.port_row.is_visible() && imp.tls_section.is_visible() && imp.ssh_section.is_visible());
assert_eq!(
imp.tls_section.form_state().mode,
TlsMode::VerifyFull,
TlsMode::Require,
"the network's TLS came back"
);
}
Expand All @@ -1614,9 +1615,10 @@ fn a_path_typed_as_the_host_is_a_socket_folder() {
imp.host_row.set_text("db.example.com");
assert_eq!(
imp.tls_section.form_state().mode,
TlsMode::VerifyFull,
TlsMode::Prefer,
"a host typed over the socket kept the socket's TLS off"
);
select_mode(&harness.dialog, TlsMode::VerifyFull);
imp.host_row.set_text("/var/run/postgresql");

select(&harness.dialog, "mongodb");
Expand Down
142 changes: 74 additions & 68 deletions linux/crates/app/src/ui/preferences_dialog/account_page.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2219,76 +2219,82 @@ mod tests {
drop(window);
}

#[gtk4::test]
#[ignore = "requires a tablepro-cloud stack"]
fn the_page_signs_in_through_the_browser_shows_the_account_and_signs_out() {
let read =
|name: &str| std::env::var(name).unwrap_or_else(|_| panic!("{name} is not set; see linux/docs/testing.md"));
let email = format!(
"linux-page-{}@{}",
&uuid::Uuid::new_v4().simple().to_string()[..8],
read("TABLEPRO_CLOUD_EMAIL_DOMAIN")
);
let harness = Harness::new(None);
let endpoints = Endpoints::new(&read("TABLEPRO_CLOUD_ACCOUNT_URL"), &read("TABLEPRO_CLOUD_API_URL"))
.expect("the endpoints");
let keys =
tablepro_account::EntitlementKeys::from_public_keys([read("TABLEPRO_CLOUD_ENTITLEMENT_KEY").as_str()])
.expect("the stack's key");
let machine = MachineId::derive(&format!("{}\n", uuid::Uuid::new_v4().simple())).expect("a machine ID");
let browser = Arc::new(StackBrowser {
email: email.clone(),
profile: tempfile::tempdir().expect("a browser profile"),
});
harness.ready_with(endpoints, keys, machine, browser);
let collector = crate::test_support::PostHogCollector::start();
harness.share_usage_data(&collector);
let window = OwnedWindow::presented(adw::Window::builder().default_width(900).default_height(900).build());
window.set_content(Some(&harness.page));
let shows = |title: &str| {
harness.settle();
descendants(&harness.page)
.into_iter()
.filter_map(|widget| widget.downcast::<adw::ActionRow>().ok())
.any(|row| row.title() == title)
};
// nextest leaves `::cloud_stack::` out of every run: CI cannot reach the private backend.
mod cloud_stack {
use super::*;

#[gtk4::test]
#[ignore = "requires a tablepro-cloud stack"]
fn the_page_signs_in_through_the_browser_shows_the_account_and_signs_out() {
let read = |name: &str| {
std::env::var(name).unwrap_or_else(|_| panic!("{name} is not set; see linux/docs/testing.md"))
};
let email = format!(
"linux-page-{}@{}",
&uuid::Uuid::new_v4().simple().to_string()[..8],
read("TABLEPRO_CLOUD_EMAIL_DOMAIN")
);
let harness = Harness::new(None);
let endpoints = Endpoints::new(&read("TABLEPRO_CLOUD_ACCOUNT_URL"), &read("TABLEPRO_CLOUD_API_URL"))
.expect("the endpoints");
let keys =
tablepro_account::EntitlementKeys::from_public_keys([read("TABLEPRO_CLOUD_ENTITLEMENT_KEY").as_str()])
.expect("the stack's key");
let machine = MachineId::derive(&format!("{}\n", uuid::Uuid::new_v4().simple())).expect("a machine ID");
let browser = Arc::new(StackBrowser {
email: email.clone(),
profile: tempfile::tempdir().expect("a browser profile"),
});
harness.ready_with(endpoints, keys, machine, browser);
let collector = crate::test_support::PostHogCollector::start();
harness.share_usage_data(&collector);
let window = OwnedWindow::presented(adw::Window::builder().default_width(900).default_height(900).build());
window.set_content(Some(&harness.page));
let shows = |title: &str| {
harness.settle();
descendants(&harness.page)
.into_iter()
.filter_map(|widget| widget.downcast::<adw::ActionRow>().ok())
.any(|row| row.title() == title)
};

harness.button(&harness.page, "Sign In…").emit_clicked();
wait_until(Duration::from_secs(120), || shows("Starter") && shows("Ada's laptop"))
.expect("signed in, with the plan and this computer");
harness.button(&harness.page, "Sign In…").emit_clicked();
wait_until(Duration::from_secs(120), || shows("Starter") && shows("Ada's laptop"))
.expect("signed in, with the plan and this computer");

let imp = harness.page.imp();
assert_eq!(harness.titles(&imp.account_group), [email.as_str(), "Manage Account"]);
assert!(
harness
.row("Ada's laptop")
.subtitle()
.is_some_and(|text| text.starts_with("This computer"))
);
// An admin's grant, as the browser step writes it: no cycle and no Billing….
assert_eq!(harness.row("Starter").subtitle().as_deref(), Some("Personal · Active"));
assert!(
descendants(&harness.row("Starter"))
.iter()
.all(|widget| !widget.is::<gtk4::Button>())
);
assert!(
harness
.titles(&imp.offline_group)
.iter()
.any(|title| title.starts_with("Verified"))
);
let imp = harness.page.imp();
assert_eq!(harness.titles(&imp.account_group), [email.as_str(), "Manage Account"]);
assert!(
harness
.row("Ada's laptop")
.subtitle()
.is_some_and(|text| text.starts_with("This computer"))
);
// An admin's grant, as the browser step writes it: no cycle and no Billing….
assert_eq!(harness.row("Starter").subtitle().as_deref(), Some("Personal · Active"));
assert!(
descendants(&harness.row("Starter"))
.iter()
.all(|widget| !widget.is::<gtk4::Button>())
);
assert!(
harness
.titles(&imp.offline_group)
.iter()
.any(|title| title.starts_with("Verified"))
);

harness.button(&imp.account_group.get(), "Sign Out").emit_clicked();
wait_until(WAIT, || shows("Sign In to TablePro")).expect("signed out");
wait_until(WAIT, || !collector.named("signed_out").is_empty()).expect("the sign-out sent");
let identify = collector.named("$identify");
assert_eq!(identify.len(), 1);
let account = identify[0]["distinct_id"].as_str().unwrap_or_default().to_owned();
assert!(account.starts_with("acct_"), "{account}");
assert_eq!(collector.named("signed_in")[0]["distinct_id"], account.as_str());
assert_eq!(collector.named("signed_in")[0]["properties"]["method"], "interactive");
assert_eq!(collector.named("signed_out")[0]["distinct_id"], account.as_str());
drop(window);
harness.button(&imp.account_group.get(), "Sign Out").emit_clicked();
wait_until(WAIT, || shows("Sign In to TablePro")).expect("signed out");
wait_until(WAIT, || !collector.named("signed_out").is_empty()).expect("the sign-out sent");
let identify = collector.named("$identify");
assert_eq!(identify.len(), 1);
let account = identify[0]["distinct_id"].as_str().unwrap_or_default().to_owned();
assert!(account.starts_with("acct_"), "{account}");
assert_eq!(collector.named("signed_in")[0]["distinct_id"], account.as_str());
assert_eq!(collector.named("signed_in")[0]["properties"]["method"], "interactive");
assert_eq!(collector.named("signed_out")[0]["distinct_id"], account.as_str());
drop(window);
}
}
}
2 changes: 1 addition & 1 deletion linux/docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ Three reasons are allowed:
- `#[ignore = "requires docker"]` for testcontainers suites.
- `#[ignore = "requires a Secret Service"]` for tests that talk to a keyring.
- `#[ignore = "requires a SQL Server on this Windows machine"]` for the SQL Server driver's integrated login through SSPI (`tests/integration/windows_server.rs`). SQL Server installs on x64 Windows only, so `core.yml`'s `sql-server-windows` job runs them on `windows-2025`: `tests/windows/sql-server.ps1 -Install` sets up SQL Server 2022 Express with a self-signed certificate, the `sspi` tests sign in over TDS 7.4 and strict TLS, `-ForceStrict` turns on Force Strict Encryption, and the `force_strict` test checks that a TDS 7.4 login is refused and a strict one gets in. The runner is in no domain, so SSPI signs in with NTLM.
- `#[ignore = "requires a tablepro-cloud stack"]` for the account's tests against a running backend ([below](#tablepro-cloud-stack)).
- `#[ignore = "requires a tablepro-cloud stack"]` for the account's tests against a running backend ([below](#tablepro-cloud-stack)). They live in a `cloud_stack` test binary or module, which nextest's default filter in `.config/nextest.toml` leaves out, so the Docker jobs' `--run-ignored only` never picks them up.

The workspace denies `clippy::ignore_without_reason`, so a bare `#[ignore]` fails the build.

Expand Down
Loading