@@ -62,6 +62,312 @@ func buildGraphJSON(t *testing.T, nodes []Node, rels []Relationship) string {
6262 return f .Name ()
6363}
6464
65+ // ── getStr ────────────────────────────────────────────────────────────────────
66+
67+ func TestGetStr (t * testing.T ) {
68+ m := map [string ]interface {}{"name" : "foo" , "num" : 42 , "empty" : "" }
69+ if got := getStr (m , "name" ); got != "foo" {
70+ t .Errorf ("got %q, want %q" , got , "foo" )
71+ }
72+ if got := getStr (m , "num" ); got != "" {
73+ t .Errorf ("non-string: got %q, want empty" , got )
74+ }
75+ if got := getStr (m , "missing" ); got != "" {
76+ t .Errorf ("missing key: got %q, want empty" , got )
77+ }
78+ if got := getStr (m , "empty" ); got != "" {
79+ t .Errorf ("empty string: got %q, want empty" , got )
80+ }
81+ }
82+
83+ // ── getNum ────────────────────────────────────────────────────────────────────
84+
85+ func TestGetNum (t * testing.T ) {
86+ m := map [string ]interface {}{"f64" : float64 (7 ), "i" : 9 , "str" : "x" }
87+ if got := getNum (m , "f64" ); got != 7 {
88+ t .Errorf ("float64: got %d, want 7" , got )
89+ }
90+ if got := getNum (m , "i" ); got != 9 {
91+ t .Errorf ("int: got %d, want 9" , got )
92+ }
93+ if got := getNum (m , "str" ); got != 0 {
94+ t .Errorf ("wrong type: got %d, want 0" , got )
95+ }
96+ if got := getNum (m , "missing" ); got != 0 {
97+ t .Errorf ("missing key: got %d, want 0" , got )
98+ }
99+ }
100+
101+ // ── mermaidID ─────────────────────────────────────────────────────────────────
102+
103+ func TestMermaidID (t * testing.T ) {
104+ cases := []struct { in , want string }{
105+ {"fn:src/foo.go:bar" , "fn_src_foo_go_bar" },
106+ {"hello_world" , "hello_world" },
107+ {"ABC123" , "ABC123" },
108+ {"" , "node" },
109+ {"---" , "___" },
110+ }
111+ for _ , tc := range cases {
112+ got := mermaidID (tc .in )
113+ if got != tc .want {
114+ t .Errorf ("mermaidID(%q) = %q, want %q" , tc .in , got , tc .want )
115+ }
116+ }
117+ }
118+
119+ // ── generateSlug ─────────────────────────────────────────────────────────────
120+
121+ func TestGenerateSlug_File (t * testing.T ) {
122+ n := Node {Properties : map [string ]interface {}{"path" : "src/main.go" }}
123+ got := generateSlug (n , "File" )
124+ if ! strings .HasPrefix (got , "file-" ) {
125+ t .Errorf ("File slug: got %q, want prefix 'file-'" , got )
126+ }
127+ // empty path → empty slug
128+ n2 := Node {Properties : map [string ]interface {}{}}
129+ if got2 := generateSlug (n2 , "File" ); got2 != "" {
130+ t .Errorf ("empty path File slug: got %q, want empty" , got2 )
131+ }
132+ }
133+
134+ func TestGenerateSlug_Function (t * testing.T ) {
135+ n := Node {Properties : map [string ]interface {}{"name" : "run" , "filePath" : "internal/api/handler.go" }}
136+ got := generateSlug (n , "Function" )
137+ if ! strings .HasPrefix (got , "fn-" ) {
138+ t .Errorf ("Function slug with path: got %q, want prefix 'fn-'" , got )
139+ }
140+ n2 := Node {Properties : map [string ]interface {}{"name" : "run" }}
141+ got2 := generateSlug (n2 , "Function" )
142+ if ! strings .HasPrefix (got2 , "fn-" ) {
143+ t .Errorf ("Function slug without path: got %q, want prefix 'fn-'" , got2 )
144+ }
145+ n3 := Node {Properties : map [string ]interface {}{}}
146+ if got3 := generateSlug (n3 , "Function" ); got3 != "" {
147+ t .Errorf ("empty name: got %q, want empty" , got3 )
148+ }
149+ }
150+
151+ func TestGenerateSlug_ClassTypeLabels (t * testing.T ) {
152+ for _ , label := range []string {"Class" , "Type" } {
153+ prefix := strings .ToLower (label ) + "-"
154+ n := Node {Properties : map [string ]interface {}{"name" : "MyEntity" , "filePath" : "src/foo.go" }}
155+ got := generateSlug (n , label )
156+ if ! strings .HasPrefix (got , prefix ) {
157+ t .Errorf ("%s slug: got %q, want prefix %q" , label , got , prefix )
158+ }
159+ n2 := Node {Properties : map [string ]interface {}{"name" : "MyEntity" }}
160+ got2 := generateSlug (n2 , label )
161+ if ! strings .HasPrefix (got2 , prefix ) {
162+ t .Errorf ("%s slug without path: got %q, want prefix %q" , label , got2 , prefix )
163+ }
164+ n3 := Node {Properties : map [string ]interface {}{}}
165+ if got3 := generateSlug (n3 , label ); got3 != "" {
166+ t .Errorf ("%s empty name: got %q, want empty" , label , got3 )
167+ }
168+ }
169+ }
170+
171+ func TestGenerateSlug_DomainSubdomain (t * testing.T ) {
172+ dn := Node {Properties : map [string ]interface {}{"name" : "auth" }}
173+ if got := generateSlug (dn , "Domain" ); ! strings .HasPrefix (got , "domain-" ) {
174+ t .Errorf ("Domain: got %q, want prefix 'domain-'" , got )
175+ }
176+ sn := Node {Properties : map [string ]interface {}{"name" : "users" }}
177+ if got := generateSlug (sn , "Subdomain" ); ! strings .HasPrefix (got , "subdomain-" ) {
178+ t .Errorf ("Subdomain: got %q, want prefix 'subdomain-'" , got )
179+ }
180+ empty := Node {Properties : map [string ]interface {}{}}
181+ if got := generateSlug (empty , "Domain" ); got != "" {
182+ t .Errorf ("Domain empty name: got %q, want empty" , got )
183+ }
184+ if got := generateSlug (empty , "Subdomain" ); got != "" {
185+ t .Errorf ("Subdomain empty name: got %q, want empty" , got )
186+ }
187+ }
188+
189+ func TestGenerateSlug_Directory (t * testing.T ) {
190+ n := Node {Properties : map [string ]interface {}{"path" : "internal/api" }}
191+ if got := generateSlug (n , "Directory" ); ! strings .HasPrefix (got , "dir-" ) {
192+ t .Errorf ("Directory: got %q, want prefix 'dir-'" , got )
193+ }
194+ // path containing /app/repo-root/ → empty
195+ n2 := Node {Properties : map [string ]interface {}{"path" : "/app/repo-root/internal" }}
196+ if got := generateSlug (n2 , "Directory" ); got != "" {
197+ t .Errorf ("repo-root path: got %q, want empty" , got )
198+ }
199+ // empty path → empty
200+ n3 := Node {Properties : map [string ]interface {}{}}
201+ if got := generateSlug (n3 , "Directory" ); got != "" {
202+ t .Errorf ("empty path: got %q, want empty" , got )
203+ }
204+ }
205+
206+ func TestGenerateSlug_Unknown (t * testing.T ) {
207+ n := Node {Properties : map [string ]interface {}{"name" : "foo" }}
208+ if got := generateSlug (n , "Unknown" ); got != "" {
209+ t .Errorf ("unknown label: got %q, want empty" , got )
210+ }
211+ }
212+
213+ // ── node-type rendering ───────────────────────────────────────────────────────
214+
215+ // TestRunClassNode verifies that a Class node generates a markdown file
216+ // containing class-specific frontmatter fields.
217+ func TestRunClassNode (t * testing.T ) {
218+ nodes := []Node {
219+ {
220+ ID : "class:src/auth.go:UserAuth" ,
221+ Labels : []string {"Class" },
222+ Properties : map [string ]interface {}{
223+ "name" : "UserAuth" ,
224+ "filePath" : "src/auth.go" ,
225+ "startLine" : float64 (10 ),
226+ "endLine" : float64 (50 ),
227+ "language" : "go" ,
228+ },
229+ },
230+ }
231+ graphFile := buildGraphJSON (t , nodes , nil )
232+ outDir := t .TempDir ()
233+ if err := Run (graphFile , outDir , "myrepo" , "https://github.com/example/myrepo" , 0 ); err != nil {
234+ t .Fatalf ("Run: %v" , err )
235+ }
236+ entries , _ := os .ReadDir (outDir )
237+ if len (entries ) != 1 {
238+ t .Fatalf ("expected 1 output file, got %d" , len (entries ))
239+ }
240+ content , err := os .ReadFile (filepath .Join (outDir , entries [0 ].Name ()))
241+ if err != nil {
242+ t .Fatal (err )
243+ }
244+ body := string (content )
245+ for _ , want := range []string {`node_type: "Class"` , `class_name: "UserAuth"` , `language: "go"` , `start_line: 10` , `end_line: 50` } {
246+ if ! strings .Contains (body , want ) {
247+ t .Errorf ("missing %q in class output:\n %s" , want , body )
248+ }
249+ }
250+ }
251+
252+ // TestRunTypeNode verifies that a Type node generates type-specific frontmatter.
253+ func TestRunTypeNode (t * testing.T ) {
254+ nodes := []Node {
255+ {
256+ ID : "type:src/types.go:UserID" ,
257+ Labels : []string {"Type" },
258+ Properties : map [string ]interface {}{
259+ "name" : "UserID" ,
260+ "filePath" : "src/types.go" ,
261+ },
262+ },
263+ }
264+ graphFile := buildGraphJSON (t , nodes , nil )
265+ outDir := t .TempDir ()
266+ if err := Run (graphFile , outDir , "myrepo" , "" , 0 ); err != nil {
267+ t .Fatalf ("Run: %v" , err )
268+ }
269+ entries , _ := os .ReadDir (outDir )
270+ if len (entries ) != 1 {
271+ t .Fatalf ("expected 1 output file, got %d" , len (entries ))
272+ }
273+ content , _ := os .ReadFile (filepath .Join (outDir , entries [0 ].Name ()))
274+ body := string (content )
275+ for _ , want := range []string {`node_type: "Type"` , `type_name: "UserID"` } {
276+ if ! strings .Contains (body , want ) {
277+ t .Errorf ("missing %q in type output:\n %s" , want , body )
278+ }
279+ }
280+ }
281+
282+ // TestRunDomainNode verifies that a Domain node generates domain-specific frontmatter.
283+ func TestRunDomainNode (t * testing.T ) {
284+ nodes := []Node {
285+ {
286+ ID : "domain:auth" ,
287+ Labels : []string {"Domain" },
288+ Properties : map [string ]interface {}{
289+ "name" : "auth" ,
290+ "description" : "Authentication domain" ,
291+ },
292+ },
293+ }
294+ graphFile := buildGraphJSON (t , nodes , nil )
295+ outDir := t .TempDir ()
296+ if err := Run (graphFile , outDir , "myrepo" , "" , 0 ); err != nil {
297+ t .Fatalf ("Run: %v" , err )
298+ }
299+ entries , _ := os .ReadDir (outDir )
300+ if len (entries ) != 1 {
301+ t .Fatalf ("expected 1 output file, got %d" , len (entries ))
302+ }
303+ content , _ := os .ReadFile (filepath .Join (outDir , entries [0 ].Name ()))
304+ body := string (content )
305+ for _ , want := range []string {`node_type: "Domain"` , `domain: "auth"` , `summary: "Authentication domain"` } {
306+ if ! strings .Contains (body , want ) {
307+ t .Errorf ("missing %q in domain output:\n %s" , want , body )
308+ }
309+ }
310+ }
311+
312+ // TestRunSubdomainNode verifies that a Subdomain node generates subdomain frontmatter.
313+ func TestRunSubdomainNode (t * testing.T ) {
314+ nodes := []Node {
315+ {
316+ ID : "subdomain:users" ,
317+ Labels : []string {"Subdomain" },
318+ Properties : map [string ]interface {}{
319+ "name" : "users" ,
320+ },
321+ },
322+ }
323+ graphFile := buildGraphJSON (t , nodes , nil )
324+ outDir := t .TempDir ()
325+ if err := Run (graphFile , outDir , "myrepo" , "" , 0 ); err != nil {
326+ t .Fatalf ("Run: %v" , err )
327+ }
328+ entries , _ := os .ReadDir (outDir )
329+ if len (entries ) != 1 {
330+ t .Fatalf ("expected 1 output file, got %d" , len (entries ))
331+ }
332+ content , _ := os .ReadFile (filepath .Join (outDir , entries [0 ].Name ()))
333+ body := string (content )
334+ for _ , want := range []string {`node_type: "Subdomain"` , `subdomain: "users"` } {
335+ if ! strings .Contains (body , want ) {
336+ t .Errorf ("missing %q in subdomain output:\n %s" , want , body )
337+ }
338+ }
339+ }
340+
341+ // TestRunDirectoryNode verifies that a Directory node generates directory frontmatter.
342+ func TestRunDirectoryNode (t * testing.T ) {
343+ nodes := []Node {
344+ {
345+ ID : "dir:internal/api" ,
346+ Labels : []string {"Directory" },
347+ Properties : map [string ]interface {}{
348+ "name" : "api" ,
349+ "path" : "internal/api" ,
350+ },
351+ },
352+ }
353+ graphFile := buildGraphJSON (t , nodes , nil )
354+ outDir := t .TempDir ()
355+ if err := Run (graphFile , outDir , "myrepo" , "" , 0 ); err != nil {
356+ t .Fatalf ("Run: %v" , err )
357+ }
358+ entries , _ := os .ReadDir (outDir )
359+ if len (entries ) != 1 {
360+ t .Fatalf ("expected 1 output file, got %d" , len (entries ))
361+ }
362+ content , _ := os .ReadFile (filepath .Join (outDir , entries [0 ].Name ()))
363+ body := string (content )
364+ for _ , want := range []string {`node_type: "Directory"` } {
365+ if ! strings .Contains (body , want ) {
366+ t .Errorf ("missing %q in directory output:\n %s" , want , body )
367+ }
368+ }
369+ }
370+
65371// TestSlugCollisionResolution verifies that when two nodes produce the same
66372// base slug, the second gets a "-2" suffix, AND that a third node which
67373// naturally produces that same "-2" slug does not silently collide with it.
0 commit comments