@@ -29,6 +29,7 @@ pub fn init(template: &str, path: Option<&str>) -> Result<()> {
2929#[ cfg( test) ]
3030mod tests {
3131 use super :: * ;
32+ use std:: collections:: BTreeSet ;
3233
3334 #[ test]
3435 fn test_unknown_template ( ) {
@@ -97,4 +98,120 @@ mod tests {
9798 assert ! ( target. join( "wit/deps/http/types.wit" ) . exists( ) ) ;
9899 assert ! ( target. join( "wit/deps/shim/dns.wit" ) . exists( ) ) ;
99100 }
101+
102+ // ── Template ↔ Fixture consistency tests ─────────────────────
103+ //
104+ // These tests ensure the embedded template content stays in sync with
105+ // the fixture directories that integration tests build. A drift between
106+ // template and fixture means integration tests validate stale code.
107+
108+ /// Collect all relative file paths under `dir`, excluding build artifacts.
109+ fn collect_files ( dir : & std:: path:: Path ) -> BTreeSet < String > {
110+ let mut files = BTreeSet :: new ( ) ;
111+ for entry in walkdir:: WalkDir :: new ( dir)
112+ . into_iter ( )
113+ . filter_map ( |e| e. ok ( ) )
114+ . filter ( |e| e. file_type ( ) . is_file ( ) )
115+ {
116+ let rel = entry
117+ . path ( )
118+ . strip_prefix ( dir)
119+ . unwrap ( )
120+ . to_string_lossy ( )
121+ . to_string ( ) ;
122+ // Skip build artifacts that aren't part of the template
123+ if rel. starts_with ( "target/" ) || rel == "Cargo.lock" {
124+ continue ;
125+ }
126+ files. insert ( rel) ;
127+ }
128+ files
129+ }
130+
131+ /// Normalize fixture content so it can be compared to template output.
132+ ///
133+ /// Fixtures use their directory name as the project name (e.g.
134+ /// "async-rust-template") whereas templates use the placeholder
135+ /// "my-async-handler". The Go fixture also has a local `replace`
136+ /// directive needed for workspace builds that the template omits.
137+ fn normalize_fixture_content (
138+ content : & str ,
139+ fixture_name : & str ,
140+ ) -> String {
141+ content
142+ . replace ( fixture_name, "my-async-handler" )
143+ // Go fixture has a local replace directive for workspace builds
144+ . replace (
145+ "\n replace github.com/anthropics/warpgrid/packages/warpgrid-go => ../../../packages/warpgrid-go\n " ,
146+ "" ,
147+ )
148+ }
149+
150+ /// Scaffold a template and compare every generated file against the
151+ /// corresponding fixture file. Fails if content differs or if the
152+ /// fixture has files the template doesn't produce (or vice versa).
153+ ///
154+ /// Known differences (project name, local replace directives) are
155+ /// normalized before comparison.
156+ fn assert_template_matches_fixture ( template_name : & str , fixture_subdir : & str ) {
157+ let dir = tempfile:: tempdir ( ) . unwrap ( ) ;
158+ let scaffolded = dir. path ( ) . join ( "project" ) ;
159+ crate :: templates:: scaffold ( template_name, & scaffolded) . unwrap ( ) ;
160+
161+ let fixture_dir = std:: path:: Path :: new ( env ! ( "CARGO_MANIFEST_DIR" ) )
162+ . parent ( )
163+ . unwrap ( )
164+ . parent ( )
165+ . unwrap ( )
166+ . join ( "tests/fixtures" )
167+ . join ( fixture_subdir) ;
168+
169+ let scaffolded_files = collect_files ( & scaffolded) ;
170+ let fixture_files = collect_files ( & fixture_dir) ;
171+
172+ // Check for files in fixture but missing from template
173+ let missing_from_template: BTreeSet < _ > =
174+ fixture_files. difference ( & scaffolded_files) . collect ( ) ;
175+ assert ! (
176+ missing_from_template. is_empty( ) ,
177+ "Fixture '{fixture_subdir}' has files not produced by template '{template_name}': {missing_from_template:?}"
178+ ) ;
179+
180+ // Check for files in template but missing from fixture
181+ let missing_from_fixture: BTreeSet < _ > =
182+ scaffolded_files. difference ( & fixture_files) . collect ( ) ;
183+ assert ! (
184+ missing_from_fixture. is_empty( ) ,
185+ "Template '{template_name}' produces files not in fixture '{fixture_subdir}': {missing_from_fixture:?}"
186+ ) ;
187+
188+ // Compare content of every file (normalizing known differences)
189+ for file in & scaffolded_files {
190+ let scaffolded_content =
191+ std:: fs:: read_to_string ( scaffolded. join ( file) ) . unwrap ( ) ;
192+ let fixture_content =
193+ std:: fs:: read_to_string ( fixture_dir. join ( file) ) . unwrap ( ) ;
194+ let normalized_fixture =
195+ normalize_fixture_content ( & fixture_content, fixture_subdir) ;
196+ assert_eq ! (
197+ scaffolded_content, normalized_fixture,
198+ "Content mismatch in '{file}' between template '{template_name}' and fixture '{fixture_subdir}'"
199+ ) ;
200+ }
201+ }
202+
203+ #[ test]
204+ fn test_async_rust_template_matches_fixture ( ) {
205+ assert_template_matches_fixture ( "async-rust" , "async-rust-template" ) ;
206+ }
207+
208+ #[ test]
209+ fn test_async_go_template_matches_fixture ( ) {
210+ assert_template_matches_fixture ( "async-go" , "async-go-template" ) ;
211+ }
212+
213+ #[ test]
214+ fn test_async_ts_template_matches_fixture ( ) {
215+ assert_template_matches_fixture ( "async-ts" , "async-ts-template" ) ;
216+ }
100217}
0 commit comments