metabase_smoketest/
metabase-smoketest.rs
1use std::time::Duration;
11
12use anyhow::{Context, bail};
13use itertools::Itertools;
14use tokio::net::TcpStream;
15use tokio_postgres::NoTls;
16use tracing::debug;
17
18use mz_metabase::{
19 DatabaseMetadata, LoginRequest, SetupDatabase, SetupDatabaseDetails, SetupPrefs, SetupRequest,
20 SetupUser, Table, TableField,
21};
22use mz_ore::retry::Retry;
23use mz_ore::task;
24
25const DUMMY_EMAIL: &str = "ci@materialize.io";
26const DUMMY_PASSWORD: &str = "dummydummy1";
27
28async fn connect_materialized() -> Result<tokio_postgres::Client, anyhow::Error> {
29 Retry::default()
30 .retry_async(|_| async {
31 let res = TcpStream::connect("materialized:6875").await;
32 if let Err(e) = &res {
33 debug!("error connecting to materialized: {}", e);
34 }
35 res
36 })
37 .await?;
38 let (client, conn) = tokio_postgres::connect(
39 "postgres://materialize@materialized:6875/materialize",
40 NoTls,
41 )
42 .await
43 .context("failed connecting to materialized")?;
44 task::spawn(|| "metabase_smoketest_mz", async {
45 if let Err(e) = conn.await {
46 panic!("postgres connection error: {}", e);
47 }
48 });
49 Ok(client)
50}
51
52async fn connect_metabase() -> Result<mz_metabase::Client, anyhow::Error> {
53 let mut client = mz_metabase::Client::new("http://metabase:3000")
54 .context("failed creating metabase client")?;
55 let setup_token = Retry::default()
56 .max_duration(Duration::from_secs(30))
57 .retry_async(|_| async {
58 let res = client.session_properties().await;
59 if let Err(e) = &res {
60 debug!("error connecting to metabase: {}", e);
61 }
62 res.map(|res| res.setup_token)
63 })
64 .await?;
65 let session_id = match setup_token {
66 None => {
67 let req = LoginRequest {
68 username: DUMMY_EMAIL.into(),
69 password: DUMMY_PASSWORD.into(),
70 };
71 client.login(&req).await?.id
72 }
73 Some(setup_token) => {
74 let req = &SetupRequest {
75 allow_tracking: false,
76 database: SetupDatabase {
77 engine: "postgres".into(),
78 name: "Materialize".into(),
79 details: SetupDatabaseDetails {
80 host: "materialized".into(),
81 port: 6875,
82 dbname: "materialize".into(),
83 user: "materialize".into(),
84 },
85 },
86 token: setup_token,
87 prefs: SetupPrefs {
88 site_name: "Materialize".into(),
89 },
90 user: SetupUser {
91 email: DUMMY_EMAIL.into(),
92 first_name: "Materialize".into(),
93 last_name: "CI".into(),
94 password: DUMMY_PASSWORD.into(),
95 site_name: "Materialize".into(),
96 },
97 };
98 client.setup(req).await?.id
99 }
100 };
101 client.set_session_id(session_id);
102 Ok(client)
103}
104
105#[tokio::main]
106async fn main() -> Result<(), anyhow::Error> {
107 mz_ore::test::init_logging();
108
109 let pgclient = connect_materialized().await?;
110 pgclient
111 .batch_execute(
112 "CREATE OR REPLACE MATERIALIZED VIEW orders (id, date, quantity, total) AS
113 VALUES (1, '2020-01-03'::date, 6, 10.99), (2, '2020-01-04'::date, 4, 7.48)",
114 )
115 .await?;
116
117 let metabase_client = connect_metabase().await?;
118
119 let databases = metabase_client.databases().await?;
120 debug!("Databases: {:#?}", databases);
121
122 let database_names: Vec<_> = databases.iter().map(|d| &d.name).sorted().collect();
123 assert_eq!(database_names, &["Materialize", "Sample Dataset"]);
124
125 let mzdb = databases.iter().find(|d| d.name == "Materialize").unwrap();
126 let expected_metadata = DatabaseMetadata {
127 tables: vec![Table {
128 name: "orders".into(),
129 schema: "public".into(),
130 fields: vec![
131 TableField {
132 name: "date".into(),
133 database_type: "date".into(),
134 base_type: "type/Date".into(),
135 special_type: None,
136 },
137 TableField {
138 name: "id".into(),
139 database_type: "int4".into(),
140 base_type: "type/Integer".into(),
141 special_type: None,
142 },
143 TableField {
144 name: "quantity".into(),
145 database_type: "int4".into(),
146 base_type: "type/Integer".into(),
147 special_type: None,
148 },
149 TableField {
150 name: "total".into(),
151 database_type: "numeric".into(),
152 base_type: "type/Decimal".into(),
153 special_type: None,
154 },
155 ],
156 }],
157 };
158 Retry::default()
162 .retry_async(|_| async {
163 let mut metadata = metabase_client.database_metadata(mzdb.id).await?;
164 metadata.tables.retain(|t| t.schema == "public");
165 metadata.tables.sort_by(|a, b| a.name.cmp(&b.name));
166 for t in &mut metadata.tables {
167 t.fields.sort_by(|a, b| a.name.cmp(&b.name));
168 }
169 debug!("Materialize database metadata: {:#?}", metadata);
170 if expected_metadata != metadata {
171 bail!(
172 "metadata did not match\nexpected:\n{:#?}\nactual:\n{:#?}",
173 expected_metadata,
174 metadata,
175 );
176 }
177 Ok(())
178 })
179 .await?;
180
181 println!("OK");
182 Ok(())
183}