@@ -312,6 +312,172 @@ func TestModelUnmarshalJSONInvalidData(t *testing.T) {
312312 }
313313}
314314
315+ // mockModel implements types.Model for testing ToOpenAI.
316+ type mockModel struct {
317+ id string
318+ tags []string
319+ config types.ModelConfig
320+ desc types.Descriptor
321+ }
322+
323+ func (m * mockModel ) ID () (string , error ) { return m .id , nil }
324+ func (m * mockModel ) Tags () []string { return m .tags }
325+ func (m * mockModel ) Config () (types.ModelConfig , error ) { return m .config , nil }
326+ func (m * mockModel ) Descriptor () (types.Descriptor , error ) { return m .desc , nil }
327+ func (m * mockModel ) GGUFPaths () ([]string , error ) { return nil , nil }
328+ func (m * mockModel ) SafetensorsPaths () ([]string , error ) { return nil , nil }
329+ func (m * mockModel ) DDUFPaths () ([]string , error ) { return nil , nil }
330+ func (m * mockModel ) ConfigArchivePath () (string , error ) { return "" , nil }
331+ func (m * mockModel ) MMPROJPath () (string , error ) { return "" , nil }
332+ func (m * mockModel ) ChatTemplatePath () (string , error ) { return "" , nil }
333+
334+ func TestToOpenAIWithFullConfig (t * testing.T ) {
335+ m := & mockModel {
336+ id : "sha256:abc123" ,
337+ tags : []string {"ai/smollm2:latest" },
338+ config : & types.Config {
339+ Format : "gguf" ,
340+ Quantization : "Q4_K_M" ,
341+ Parameters : "1.7B" ,
342+ Architecture : "llama" ,
343+ Size : "1.7B" ,
344+ ContextSize : int32Ptr (8192 ),
345+ },
346+ desc : types.Descriptor {},
347+ }
348+
349+ result , err := ToOpenAI (m )
350+ require .NoError (t , err )
351+
352+ assert .Equal (t , "ai/smollm2:latest" , result .ID )
353+ assert .Equal (t , "model" , result .Object )
354+ assert .Equal (t , "docker" , result .OwnedBy )
355+
356+ require .NotNil (t , result .DMR )
357+ require .NotNil (t , result .DMR .ContextWindow )
358+ assert .Equal (t , int32 (8192 ), * result .DMR .ContextWindow )
359+ assert .Equal (t , "llama" , result .DMR .Architecture )
360+ assert .Equal (t , "1.7B" , result .DMR .Parameters )
361+ assert .Equal (t , "Q4_K_M" , result .DMR .Quantization )
362+ assert .Equal (t , "1.7B" , result .DMR .Size )
363+ }
364+
365+ func TestToOpenAIWithNilConfig (t * testing.T ) {
366+ m := & mockModel {
367+ id : "sha256:abc123" ,
368+ tags : []string {"ai/model:latest" },
369+ desc : types.Descriptor {},
370+ }
371+
372+ result , err := ToOpenAI (m )
373+ require .NoError (t , err )
374+
375+ assert .Equal (t , "ai/model:latest" , result .ID )
376+ assert .Equal (t , "model" , result .Object )
377+ assert .Equal (t , "docker" , result .OwnedBy )
378+ assert .Nil (t , result .DMR )
379+ }
380+
381+ func TestToOpenAIWithoutTags (t * testing.T ) {
382+ m := & mockModel {
383+ id : "sha256:abc123" ,
384+ desc : types.Descriptor {},
385+ config : & types.Config {
386+ Architecture : "mistral" ,
387+ },
388+ }
389+
390+ result , err := ToOpenAI (m )
391+ require .NoError (t , err )
392+
393+ assert .Equal (t , "sha256:abc123" , result .ID )
394+ require .NotNil (t , result .DMR )
395+ assert .Equal (t , "mistral" , result .DMR .Architecture )
396+ }
397+
398+ func TestToOpenAIDMROmittedWhenNilConfig (t * testing.T ) {
399+ m := & mockModel {
400+ id : "sha256:abc123" ,
401+ tags : []string {"ai/model:latest" },
402+ desc : types.Descriptor {},
403+ }
404+
405+ result , err := ToOpenAI (m )
406+ require .NoError (t , err )
407+
408+ data , err := json .Marshal (result )
409+ require .NoError (t , err )
410+
411+ var raw map [string ]interface {}
412+ err = json .Unmarshal (data , & raw )
413+ require .NoError (t , err )
414+
415+ _ , hasDMR := raw ["dmr" ]
416+ assert .False (t , hasDMR , "dmr field should be omitted when config is nil" )
417+ }
418+
419+ func TestToOpenAIContextWindowOmittedWhenNil (t * testing.T ) {
420+ m := & mockModel {
421+ id : "sha256:abc123" ,
422+ tags : []string {"ai/model:latest" },
423+ desc : types.Descriptor {},
424+ config : & types.Config {
425+ Architecture : "llama" ,
426+ },
427+ }
428+
429+ result , err := ToOpenAI (m )
430+ require .NoError (t , err )
431+
432+ require .NotNil (t , result .DMR )
433+ assert .Nil (t , result .DMR .ContextWindow )
434+
435+ data , err := json .Marshal (result )
436+ require .NoError (t , err )
437+
438+ var raw map [string ]interface {}
439+ err = json .Unmarshal (data , & raw )
440+ require .NoError (t , err )
441+
442+ dmr , ok := raw ["dmr" ].(map [string ]interface {})
443+ require .True (t , ok )
444+ _ , hasCtxWindow := dmr ["context_window" ]
445+ assert .False (t , hasCtxWindow , "context_window should be omitted when nil" )
446+ }
447+
448+ func TestToOpenAIList (t * testing.T ) {
449+ models := []types.Model {
450+ & mockModel {
451+ id : "sha256:aaa" ,
452+ tags : []string {"ai/model1:latest" },
453+ desc : types.Descriptor {},
454+ config : & types.Config {
455+ Architecture : "llama" ,
456+ Parameters : "7B" ,
457+ },
458+ },
459+ & mockModel {
460+ id : "sha256:bbb" ,
461+ tags : []string {"ai/model2:latest" },
462+ desc : types.Descriptor {},
463+ },
464+ }
465+
466+ result , err := ToOpenAIList (models )
467+ require .NoError (t , err )
468+
469+ assert .Equal (t , "list" , result .Object )
470+ require .Len (t , result .Data , 2 )
471+
472+ assert .Equal (t , "ai/model1:latest" , result .Data [0 ].ID )
473+ require .NotNil (t , result .Data [0 ].DMR )
474+ assert .Equal (t , "llama" , result .Data [0 ].DMR .Architecture )
475+ assert .Equal (t , "7B" , result .Data [0 ].DMR .Parameters )
476+
477+ assert .Equal (t , "ai/model2:latest" , result .Data [1 ].ID )
478+ assert .Nil (t , result .Data [1 ].DMR )
479+ }
480+
315481// Helper function to create int32 pointers
316482func int32Ptr (i int32 ) * int32 {
317483 return & i
0 commit comments