#![allow(clippy::unwrap_used)]
use std::sync::Arc;
use arrow::array::{RecordBatch, StringArray, UInt64Array};
use datafusion::datasource::MemTable;
use datafusion::prelude::SessionContext;
use polyc_projection::family::{
CONVERSATION_SECURITY_ENTRY, SECURITY_ATTRIBUTION, SECURITY_ATTRIBUTION_PROVENANCE,
};
const ATTRIBUTION_SQL: &str = polyc_query_model::statements::DASHBOARD_ATTRIBUTION_SQL;
fn schema(table: polyc_projection::family::TableId) -> arrow::datatypes::SchemaRef {
use polyc_projection::family::LogicalType;
let declared = CONVERSATION_SECURITY_ENTRY
.table(table)
.expect("table declared");
let fields: Vec<arrow::datatypes::Field> = declared
.fields()
.iter()
.map(|field| {
let data_type = match field.logical_type() {
LogicalType::Utf8 => arrow::datatypes::DataType::Utf8,
LogicalType::FixedBytes { len } => {
arrow::datatypes::DataType::FixedSizeBinary(i32::try_from(len).unwrap())
}
LogicalType::UInt64 => arrow::datatypes::DataType::UInt64,
LogicalType::Boolean => arrow::datatypes::DataType::Boolean,
};
arrow::datatypes::Field::new(field.name(), data_type, field.nullable())
})
.collect();
Arc::new(arrow::datatypes::Schema::new(fields))
}
fn fixed_incarnation(len: usize) -> arrow::array::FixedSizeBinaryArray {
let mut builder = arrow::array::FixedSizeBinaryBuilder::with_capacity(len, 32);
for _ in 0..len {
builder.append_value([0_u8; 32]).unwrap();
}
builder.finish()
}
fn context() -> SessionContext {
let ctx = SessionContext::new();
let attribution_schema = schema(SECURITY_ATTRIBUTION);
let attribution = RecordBatch::try_new(
Arc::clone(&attribution_schema),
vec![
Arc::new(StringArray::from(vec![
"conv-a", "conv-a", "conv-a", "conv-b",
])),
Arc::new(fixed_incarnation(4)),
Arc::new(UInt64Array::from(vec![0_u64, 1, 2, 0])),
Arc::new(StringArray::from(vec![
"turn-1", "turn-1", "turn-1", "turn-1",
])),
Arc::new(StringArray::from(vec![
"persona-a",
"persona-b",
"persona-c",
"persona-d",
])),
Arc::new(StringArray::from(vec![
"participant",
"participant",
"initiator",
"initiator",
])),
],
)
.unwrap();
ctx.register_table(
"attribution",
Arc::new(MemTable::try_new(attribution_schema, vec![vec![attribution]]).unwrap()),
)
.unwrap();
let provenance_schema = schema(SECURITY_ATTRIBUTION_PROVENANCE);
let provenance = RecordBatch::try_new(
Arc::clone(&provenance_schema),
vec![
Arc::new(StringArray::from(vec![
"conv-a", "conv-a", "conv-a", "conv-b",
])),
Arc::new(fixed_incarnation(4)),
Arc::new(UInt64Array::from(vec![0_u64, 1, 2, 0])),
Arc::new(StringArray::from(vec![
"slack", "slack", "trigger", "slack",
])),
Arc::new(StringArray::from(vec!["team", "team", "svc", "team"])),
Arc::new(StringArray::from(vec!["u1", "u2", "svc-1", "u3"])),
Arc::new(StringArray::from(vec!["Alice", "Bob", "Carol", "Dana"])),
Arc::new(StringArray::from(vec![
"edge-1", "edge-1", "edge-2", "edge-3",
])),
Arc::new(StringArray::from(vec!["", "", "", ""])),
Arc::new(StringArray::from(vec!["", "", "", ""])),
],
)
.unwrap();
ctx.register_table(
"attribution_provenance",
Arc::new(MemTable::try_new(provenance_schema, vec![vec![provenance]]).unwrap()),
)
.unwrap();
ctx
}
#[tokio::test]
async fn one_row_per_conversation_with_correct_edge_count_and_initiator() {
let ctx = context();
let df = ctx.sql(ATTRIBUTION_SQL).await.unwrap();
let batches = df.collect().await.unwrap();
let rows: usize = batches.iter().map(RecordBatch::num_rows).sum();
assert_eq!(
rows, 2,
"one row per conversation with any attribution, whatever its row count"
);
let batch = &batches[0];
let ids = batch
.column_by_name("id")
.unwrap()
.as_any()
.downcast_ref::<StringArray>()
.unwrap();
let edge_counts = batch
.column_by_name("edge_count")
.unwrap()
.as_any()
.downcast_ref::<UInt64Array>()
.unwrap();
let initiators = batch
.column_by_name("initiator_display_name")
.unwrap()
.as_any()
.downcast_ref::<StringArray>()
.unwrap();
let fleet_edge_counts = batch
.column_by_name("fleet_edge_count")
.unwrap()
.as_any()
.downcast_ref::<UInt64Array>()
.unwrap();
let a = ids
.iter()
.position(|id| id == Some("conv-a"))
.expect("conv-a present");
assert_eq!(
edge_counts.value(a),
2,
"conv-a's three attribution rows post through two DISTINCT edges — a fanout bug \
would report 3, one per attribution row"
);
assert_eq!(
initiators.value(a),
"Carol",
"conv-a's initiator is persona-c"
);
let b = ids
.iter()
.position(|id| id == Some("conv-b"))
.expect("conv-b present");
assert_eq!(edge_counts.value(b), 1);
assert_eq!(initiators.value(b), "Dana");
assert_eq!(fleet_edge_counts.value(a), 3);
assert_eq!(fleet_edge_counts.value(b), 3);
}