Skip to content

Commit 566573f

Browse files
committed
tests & plain formatter
1 parent c99e2bb commit 566573f

9 files changed

+296
-109
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/schema/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ enum-as-inner.workspace = true
3232
enum-map.workspace = true
3333
colored.workspace = true
3434
regex.workspace = true
35+
insta.workspace = true
3536

3637
[dev-dependencies]
3738
spacetimedb-lib = { path = "../lib", features = ["test"] }

crates/schema/src/auto_migrate.rs

Lines changed: 86 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -789,20 +789,19 @@ mod tests {
789789
use v9::{RawModuleDefV9Builder, TableAccess};
790790
use validate::tests::expect_identifier;
791791

792-
#[test]
793-
fn successful_auto_migration() {
794-
let mut old_builder = RawModuleDefV9Builder::new();
795-
let old_schedule_at = old_builder.add_type::<ScheduleAt>();
796-
let old_sum_ty = AlgebraicType::sum([("v1", AlgebraicType::U64)]);
797-
let old_sum_refty = old_builder.add_algebraic_type([], "sum", old_sum_ty, true);
798-
old_builder
792+
fn initial_module_def() -> ModuleDef {
793+
let mut builder = RawModuleDefV9Builder::new();
794+
let schedule_at = builder.add_type::<ScheduleAt>();
795+
let sum_ty = AlgebraicType::sum([("v1", AlgebraicType::U64)]);
796+
let sum_refty = builder.add_algebraic_type([], "sum", sum_ty, true);
797+
builder
799798
.build_table_with_new_type(
800799
"Apples",
801800
ProductType::from([
802801
("id", AlgebraicType::U64),
803802
("name", AlgebraicType::String),
804803
("count", AlgebraicType::U16),
805-
("sum", old_sum_refty.into()),
804+
("sum", sum_refty.into()),
806805
]),
807806
true,
808807
)
@@ -812,7 +811,7 @@ mod tests {
812811
.with_index(btree([0, 1]), "id_name_index")
813812
.finish();
814813

815-
old_builder
814+
builder
816815
.build_table_with_new_type(
817816
"Bananas",
818817
ProductType::from([
@@ -825,59 +824,61 @@ mod tests {
825824
.with_access(TableAccess::Public)
826825
.finish();
827826

828-
let old_deliveries_type = old_builder
827+
let deliveries_type = builder
829828
.build_table_with_new_type(
830829
"Deliveries",
831830
ProductType::from([
832831
("scheduled_id", AlgebraicType::U64),
833-
("scheduled_at", old_schedule_at.clone()),
834-
("sum", AlgebraicType::array(old_sum_refty.into())),
832+
("scheduled_at", schedule_at.clone()),
833+
("sum", AlgebraicType::array(sum_refty.into())),
835834
]),
836835
true,
837836
)
838837
.with_auto_inc_primary_key(0)
839838
.with_index_no_accessor_name(btree(0))
840839
.with_schedule("check_deliveries", 1)
841840
.finish();
842-
old_builder.add_reducer(
841+
builder.add_reducer(
843842
"check_deliveries",
844-
ProductType::from([("a", AlgebraicType::Ref(old_deliveries_type))]),
843+
ProductType::from([("a", AlgebraicType::Ref(deliveries_type))]),
845844
None,
846845
);
847846

848-
old_builder
847+
builder
849848
.build_table_with_new_type(
850849
"Inspections",
851850
ProductType::from([
852851
("scheduled_id", AlgebraicType::U64),
853-
("scheduled_at", old_schedule_at.clone()),
852+
("scheduled_at", schedule_at.clone()),
854853
]),
855854
true,
856855
)
857856
.with_auto_inc_primary_key(0)
858857
.with_index_no_accessor_name(btree(0))
859858
.finish();
860859

861-
old_builder.add_row_level_security("SELECT * FROM Apples");
860+
builder.add_row_level_security("SELECT * FROM Apples");
862861

863-
let old_def: ModuleDef = old_builder
862+
builder
864863
.finish()
865864
.try_into()
866-
.expect("old_def should be a valid database definition");
865+
.expect("old_def should be a valid database definition")
866+
}
867867

868-
let mut new_builder = RawModuleDefV9Builder::new();
869-
let _ = new_builder.add_type::<u32>(); // reposition ScheduleAt in the typespace, should have no effect.
870-
let new_schedule_at = new_builder.add_type::<ScheduleAt>();
871-
let new_sum_ty = AlgebraicType::sum([("v1", AlgebraicType::U64), ("v2", AlgebraicType::Bool)]);
872-
let new_sum_refty = new_builder.add_algebraic_type([], "sum", new_sum_ty, true);
873-
new_builder
868+
fn updated_module_def() -> ModuleDef {
869+
let mut builder = RawModuleDefV9Builder::new();
870+
let _ = builder.add_type::<u32>(); // reposition ScheduleAt in the typespace, should have no effect.
871+
let schedule_at = builder.add_type::<ScheduleAt>();
872+
let sum_ty = AlgebraicType::sum([("v1", AlgebraicType::U64), ("v2", AlgebraicType::Bool)]);
873+
let sum_refty = builder.add_algebraic_type([], "sum", sum_ty, true);
874+
builder
874875
.build_table_with_new_type(
875876
"Apples",
876877
ProductType::from([
877878
("id", AlgebraicType::U64),
878879
("name", AlgebraicType::String),
879880
("count", AlgebraicType::U16),
880-
("sum", new_sum_refty.into()),
881+
("sum", sum_refty.into()),
881882
]),
882883
true,
883884
)
@@ -889,7 +890,7 @@ mod tests {
889890
.with_index(btree([0, 2]), "id_count_index")
890891
.finish();
891892

892-
new_builder
893+
builder
893894
.build_table_with_new_type(
894895
"Bananas",
895896
ProductType::from([
@@ -907,13 +908,13 @@ mod tests {
907908
.with_access(TableAccess::Private)
908909
.finish();
909910

910-
let new_deliveries_type = new_builder
911+
let deliveries_type = builder
911912
.build_table_with_new_type(
912913
"Deliveries",
913914
ProductType::from([
914915
("scheduled_id", AlgebraicType::U64),
915-
("scheduled_at", new_schedule_at.clone()),
916-
("sum", AlgebraicType::array(new_sum_refty.into())),
916+
("scheduled_at", schedule_at.clone()),
917+
("sum", AlgebraicType::array(sum_refty.into())),
917918
]),
918919
true,
919920
)
@@ -922,18 +923,18 @@ mod tests {
922923
// remove schedule def
923924
.finish();
924925

925-
new_builder.add_reducer(
926+
builder.add_reducer(
926927
"check_deliveries",
927-
ProductType::from([("a", AlgebraicType::Ref(new_deliveries_type))]),
928+
ProductType::from([("a", AlgebraicType::Ref(deliveries_type))]),
928929
None,
929930
);
930931

931-
let new_inspections_type = new_builder
932+
let new_inspections_type = builder
932933
.build_table_with_new_type(
933934
"Inspections",
934935
ProductType::from([
935936
("scheduled_id", AlgebraicType::U64),
936-
("scheduled_at", new_schedule_at.clone()),
937+
("scheduled_at", schedule_at.clone()),
937938
]),
938939
true,
939940
)
@@ -944,28 +945,33 @@ mod tests {
944945
.finish();
945946

946947
// add reducer.
947-
new_builder.add_reducer(
948+
builder.add_reducer(
948949
"perform_inspection",
949950
ProductType::from([("a", AlgebraicType::Ref(new_inspections_type))]),
950951
None,
951952
);
952953

953954
// Add new table
954-
new_builder
955+
builder
955956
.build_table_with_new_type("Oranges", ProductType::from([("id", AlgebraicType::U32)]), true)
956957
.with_index(btree(0), "id_index")
957958
.with_column_sequence(0)
958959
.with_unique_constraint(0)
959960
.with_primary_key(0)
960961
.finish();
961962

962-
new_builder.add_row_level_security("SELECT * FROM Bananas");
963+
builder.add_row_level_security("SELECT * FROM Bananas");
963964

964-
let new_def: ModuleDef = new_builder
965+
builder
965966
.finish()
966967
.try_into()
967-
.expect("new_def should be a valid database definition");
968+
.expect("new_def should be a valid database definition")
969+
}
968970

971+
#[test]
972+
fn successful_auto_migration() {
973+
let old_def = initial_module_def();
974+
let new_def = updated_module_def();
969975
let plan = ponder_auto_migrate(&old_def, &new_def).expect("auto migration should succeed");
970976

971977
let apples = expect_identifier("Apples");
@@ -1413,4 +1419,45 @@ mod tests {
14131419
// but different columns from an old one.
14141420
// We've left the check in, just in case this changes in the future.
14151421
}
1422+
#[test]
1423+
fn print_empty_to_populated_schema_migration() {
1424+
// Start with completely empty schema
1425+
let old_builder = RawModuleDefV9Builder::new();
1426+
let old_def: ModuleDef = old_builder
1427+
.finish()
1428+
.try_into()
1429+
.expect("old_def should be a valid database definition");
1430+
1431+
let new_def = initial_module_def();
1432+
let plan = ponder_migrate(&old_def, &new_def).expect("auto migration should succeed");
1433+
1434+
insta::assert_snapshot!(
1435+
"empty_to_populated_migration",
1436+
plan.pretty_print(false).expect("should pretty print")
1437+
);
1438+
}
1439+
1440+
#[test]
1441+
fn print_supervised_migration() {
1442+
let old_def = initial_module_def();
1443+
let new_def = updated_module_def();
1444+
let plan = ponder_migrate(&old_def, &new_def).expect("auto migration should succeed");
1445+
1446+
insta::assert_snapshot!(
1447+
"updated pretty print",
1448+
plan.pretty_print(false).expect("should pretty print")
1449+
);
1450+
}
1451+
1452+
#[test]
1453+
fn no_color_print_supervised_migration() {
1454+
let old_def = initial_module_def();
1455+
let new_def = updated_module_def();
1456+
let plan = ponder_migrate(&old_def, &new_def).expect("auto migration should succeed");
1457+
1458+
insta::assert_snapshot!(
1459+
"updated pretty print no color",
1460+
plan.pretty_print(true).expect("should pretty print")
1461+
);
1462+
}
14161463
}

crates/schema/src/auto_migrate/ansi_formatter.rs

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::fmt;
22

3-
use colored::{Color, ColoredString, Colorize as _};
3+
use colored::{Color, Colorize as _};
44
use spacetimedb_lib::{db::raw_def::v9::TableAccess, AlgebraicType};
55
use spacetimedb_sats::algebraic_type::fmt::fmt_algebraic_type;
66

@@ -19,24 +19,22 @@ pub struct ColorScheme {
1919
pub table_name: Color,
2020
pub column_type: Color,
2121
pub section_header: Color,
22-
pub private_access: Color,
23-
pub public_access: Color,
22+
pub access: Color,
2423
pub warning: Color,
2524
}
2625

2726
impl Default for ColorScheme {
2827
fn default() -> Self {
2928
Self {
30-
created: Color::Green, // Green = success/new (calmer than bright)
31-
removed: Color::Red, // Red = deletion/removal
32-
changed: Color::Yellow, // Yellow = caution/change
33-
header: Color::BrightBlue, // Bright Blue = strong visual separation
34-
table_name: Color::Cyan, // Cyan = neutral identifier
35-
column_type: Color::Magenta, // Magenta = type hints (visually distinct)
36-
section_header: Color::Blue, // Blue = soft structural sections
37-
private_access: Color::BrightBlack, // Grey = restricted/internal
38-
public_access: Color::BrightGreen, // Bright Green = accessible, open
39-
warning: Color::BrightYellow, // Bright Yellow = strong caution
29+
created: Color::Green,
30+
removed: Color::BrightRed,
31+
changed: Color::Yellow,
32+
header: Color::BrightBlue,
33+
table_name: Color::Cyan,
34+
column_type: Color::Magenta,
35+
section_header: Color::Blue,
36+
access: Color::BrightGreen,
37+
warning: Color::Red,
4038
}
4139
}
4240
}
@@ -67,6 +65,7 @@ impl AnsiFormatter {
6765
/// Add a line with proper indentation
6866
fn add_line(&mut self, text: impl AsRef<str>) {
6967
let indent = " ".repeat(self.indent_level);
68+
7069
self.buffer.push_str(&format!("{}{}\n", indent, text.as_ref()));
7170
}
7271

@@ -83,21 +82,26 @@ impl AnsiFormatter {
8382
}
8483

8584
/// Format a type name with consistent coloring
86-
fn format_type_name(&self, type_name: &AlgebraicType) -> ColoredString {
87-
fmt_algebraic_type(type_name).to_string().color(self.colors.column_type)
85+
fn format_type_name(&self, type_name: &AlgebraicType) -> String {
86+
fmt_algebraic_type(type_name)
87+
.to_string()
88+
.color(self.colors.column_type)
89+
.to_string()
8890
}
8991

9092
/// Format table access with consistent coloring
91-
fn format_access(&self, access: TableAccess) -> ColoredString {
93+
fn format_access(&self, access: TableAccess) -> String {
9294
match access {
93-
TableAccess::Private => "private".color(self.colors.private_access),
94-
TableAccess::Public => "public".color(self.colors.public_access),
95+
TableAccess::Private => "private",
96+
TableAccess::Public => "public",
9597
}
98+
.color(self.colors.access)
99+
.to_string()
96100
}
97101

98102
/// Format a section header
99-
fn format_section_header(&self, text: &str) -> ColoredString {
100-
text.color(self.colors.section_header).bold()
103+
fn format_section_header(&self, text: &str) -> String {
104+
text.color(self.colors.section_header).bold().to_string()
101105
}
102106

103107
/// Format an item bullet point
@@ -121,9 +125,8 @@ impl MigrationFormatter for AnsiFormatter {
121125
fn format_header(&mut self) {
122126
let header_line = "━".repeat(60);
123127
let title = "Database Migration Plan".color(self.colors.header).bold();
124-
125128
self.add_line(&header_line);
126-
self.add_line(&*title);
129+
self.add_line(title.to_string());
127130
self.add_line(&header_line);
128131
self.add_line("");
129132
}
@@ -267,7 +270,7 @@ impl MigrationFormatter for AnsiFormatter {
267270
"▸ {} access for table {} ({})",
268271
Action::Changed.format_with_color(&self.colors),
269272
access_info.table_name.color(self.colors.table_name).bold(),
270-
direction.dimmed()
273+
direction.color(self.colors.access)
271274
);
272275
self.add_line(&text);
273276
}
@@ -350,10 +353,11 @@ impl MigrationFormatter for AnsiFormatter {
350353
}
351354

352355
fn format_disconnect_warning(&mut self) {
353-
self.add_line(format!(
354-
"▸ {} All clients will be {} due to breaking schema changes",
355-
"Warning".color(self.colors.warning).bold(),
356-
"disconnected".color(self.colors.removed).bold()
357-
));
356+
self.add_line(
357+
"!!! Warning: All clients will be disconnected due to breaking schema changes"
358+
.bold()
359+
.on_color(self.colors.warning)
360+
.to_string(),
361+
);
358362
}
359363
}

0 commit comments

Comments
 (0)