max / makenotwork
1 file changed,
+85 insertions,
-0 deletions
| @@ -1809,6 +1809,91 @@ mod tests { | |||
| 1809 | 1809 | } | |
| 1810 | 1810 | ||
| 1811 | 1811 | #[tokio::test] | |
| 1812 | + | async fn promote_advances_the_tier_and_flips_the_symlink_when_gates_are_green() { | |
| 1813 | + | // The happy path. Every other promote test asserts a refusal or a red | |
| 1814 | + | // outcome, so nothing pinned what a *successful* promote actually does: | |
| 1815 | + | // deploy reaches the node, the `current` symlink flips, tier_state | |
| 1816 | + | // advances with previous_version = the version we came off, any stale | |
| 1817 | + | // partial flag clears, and the handler reports the nodes it touched. | |
| 1818 | + | use crate::topology::Gate; | |
| 1819 | + | let (mut state, _tmp) = rollback_fixture("1.0.0", "2.0.0").await; | |
| 1820 | + | ||
| 1821 | + | // Source tier `host` gates the promote on cargo_test. Satisfying it is | |
| 1822 | + | // the point of the test: the sibling case asserts an unsatisfied gate | |
| 1823 | + | // blocks, this one asserts a satisfied gate lets the promote through. | |
| 1824 | + | let mut topo = (*state.topo).clone(); | |
| 1825 | + | topo.tiers[0].gates = vec![Gate::CargoTest]; | |
| 1826 | + | state.topo = Arc::new(topo); | |
| 1827 | + | let pool = state.pool.clone(); | |
| 1828 | + | let release_root = state.topo.tiers[1].nodes[0].release_root.clone(); | |
| 1829 | + | ||
| 1830 | + | // 3.0.0 is staged on the build host and green on `host`. | |
| 1831 | + | tokio::fs::create_dir_all( | |
| 1832 | + | std::path::Path::new(&release_root) | |
| 1833 | + | .join("releases") | |
| 1834 | + | .join("3.0.0"), | |
| 1835 | + | ) | |
| 1836 | + | .await | |
| 1837 | + | .unwrap(); | |
| 1838 | + | sqlx::query("INSERT INTO versions (version, git_sha, built_at, artifact_path) VALUES ('3.0.0','sha',datetime('now'),'/tmp/staged/makenotwork')") | |
| 1839 | + | .execute(&pool).await.unwrap(); | |
| 1840 | + | sqlx::query("UPDATE tier_state SET current_version = '3.0.0' WHERE tier = 'host'") | |
| 1841 | + | .execute(&pool) | |
| 1842 | + | .await | |
| 1843 | + | .unwrap(); | |
| 1844 | + | insert_gate(&pool, "host", "3.0.0", "cargo_test", 1).await; | |
| 1845 | + | ||
| 1846 | + | // A stale flag from an earlier incident, which a clean rollout clears. | |
| 1847 | + | set_partial(&state, &tid("a"), "left over from a previous canary").await; | |
| 1848 | + | ||
| 1849 | + | let Json(body) = promote_inner(state, "a".into(), PromoteBody::default()) | |
| 1850 | + | .await | |
| 1851 | + | .expect("gates are green and the node deploys, so the promote succeeds"); | |
| 1852 | + | ||
| 1853 | + | assert_eq!(body["tier"], "a"); | |
| 1854 | + | assert_eq!(body["version"], "3.0.0"); | |
| 1855 | + | assert_eq!( | |
| 1856 | + | body["nodes_deployed"], | |
| 1857 | + | serde_json::json!(["a-local"]), | |
| 1858 | + | "the response names every node the promote reached", | |
| 1859 | + | ); | |
| 1860 | + | ||
| 1861 | + | let (cur, prev) = tier_versions(&pool, "a").await; | |
| 1862 | + | assert_eq!(cur.as_deref(), Some("3.0.0")); | |
| 1863 | + | assert_eq!( | |
| 1864 | + | prev.as_deref(), | |
| 1865 | + | Some("2.0.0"), | |
| 1866 | + | "previous_version is the version we came off, so a rollback aims at it", | |
| 1867 | + | ); | |
| 1868 | + | ||
| 1869 | + | // The node genuinely moved: the promote is not just bookkeeping. | |
| 1870 | + | let link = tokio::fs::read_link(std::path::Path::new(&release_root).join("current")) | |
| 1871 | + | .await | |
| 1872 | + | .unwrap(); | |
| 1873 | + | assert_eq!(link.to_string_lossy(), "releases/3.0.0"); | |
| 1874 | + | ||
| 1875 | + | let reason: Option<String> = | |
| 1876 | + | sqlx::query_scalar("SELECT partial_reason FROM tier_state WHERE tier = 'a'") | |
| 1877 | + | .fetch_one(&pool) | |
| 1878 | + | .await | |
| 1879 | + | .unwrap(); | |
| 1880 | + | assert_eq!( | |
| 1881 | + | reason, None, | |
| 1882 | + | "a clean full rollout clears a stale partial flag", | |
| 1883 | + | ); | |
| 1884 | + | ||
| 1885 | + | // The deploy is on the record as having succeeded, which is what the | |
| 1886 | + | // next promote's gate check and /state both read. | |
| 1887 | + | let (node, outcome): (String, String) = | |
| 1888 | + | sqlx::query_as("SELECT node, outcome FROM deploys WHERE version = '3.0.0'") | |
| 1889 | + | .fetch_one(&pool) | |
| 1890 | + | .await | |
| 1891 | + | .unwrap(); | |
| 1892 | + | assert_eq!(node, "a-local"); | |
| 1893 | + | assert_eq!(outcome, "ok"); | |
| 1894 | + | } | |
| 1895 | + | ||
| 1896 | + | #[tokio::test] | |
| 1812 | 1897 | async fn promote_fails_and_flags_the_tier_when_post_deploy_gates_are_red() { | |
| 1813 | 1898 | // The deploy reached every node, so tier_state advances (a stale | |
| 1814 | 1899 | // current_version would aim a later rollback at the wrong artifact), but |