@@ -18,6 +18,7 @@ import (
1818 "go.kenn.io/agentsview/internal/db"
1919 "go.kenn.io/agentsview/internal/dbtest"
2020 "go.kenn.io/agentsview/internal/export"
21+ "go.kenn.io/agentsview/internal/pricing"
2122)
2223
2324type exportSessionsDocument struct {
@@ -505,6 +506,81 @@ func firstExportSessionsCursor(t *testing.T) string {
505506 return doc .Cursor .Next
506507}
507508
509+ func TestExportSessionsFallbackPricingOnUnseededArchive (t * testing.T ) {
510+ dataDir := testDataDir (t )
511+ database := dbtest .OpenTestDBAt (t , filepath .Join (dataDir , "sessions.db" ))
512+ require .NoError (t , database .SetDatabaseIDForTest (
513+ context .Background (), "fallback-pricing-test-db" ))
514+
515+ model := exactFallbackPricedModel (t )
516+ insertExportSessionsTestSession (t , database , db.Session {
517+ ID : "fallback-priced" ,
518+ Project : "alpha" ,
519+ Machine : "local" ,
520+ Agent : "claude" ,
521+ StartedAt : dbtest .Ptr ("2026-06-01T10:00:00Z" ),
522+ EndedAt : dbtest .Ptr ("2026-06-01T10:10:00Z" ),
523+ MessageCount : 3 ,
524+ UserMessageCount : 2 ,
525+ })
526+ require .NoError (t , database .InsertMessages ([]db.Message {
527+ {
528+ SessionID : "fallback-priced" , Ordinal : 0 , Role : "user" ,
529+ Content : "question" , ContentLength : len ("question" ),
530+ Timestamp : "2026-06-01T10:00:00Z" ,
531+ },
532+ {
533+ SessionID : "fallback-priced" , Ordinal : 1 , Role : "assistant" ,
534+ Content : "answer" , ContentLength : len ("answer" ),
535+ Timestamp : "2026-06-01T10:05:00Z" , Model : model ,
536+ TokenUsage : json .RawMessage (
537+ `{"input_tokens":1000,"output_tokens":500}` ),
538+ },
539+ {
540+ SessionID : "fallback-priced" , Ordinal : 2 , Role : "user" ,
541+ Content : "follow up" , ContentLength : len ("follow up" ),
542+ Timestamp : "2026-06-01T10:06:00Z" ,
543+ },
544+ }), "insert messages" )
545+ require .NoError (t , database .Close (), "close seeded archive" )
546+
547+ stdout , stderr , err := executeExportSessionsCommand (
548+ newRootCommand (), "export" , "sessions" )
549+ require .NoError (t , err , "export sessions on unseeded archive" )
550+ assert .Empty (t , stderr )
551+
552+ doc := decodeExportSessionsDocument (t , stdout )
553+ require .Len (t , doc .Sessions , 1 , "exported sessions" )
554+ usage := doc .Sessions [0 ].ModelUsage
555+ require .NotNil (t , usage , "model usage" )
556+ assert .True (t , usage .HasCost ,
557+ "fallback-priced model %s should have cost" , model )
558+ assert .Greater (t , usage .CostUSD , 0.0 , "fallback-priced cost" )
559+
560+ fallback , ok := doc .Pricing ["fallback" ].(map [string ]any )
561+ require .True (t , ok , "pricing fallback block" )
562+ assert .Equal (t , true , fallback ["used" ], "fallback used" )
563+ assert .Contains (t , doc .Pricing ["source" ], "embedded" ,
564+ "pricing source provenance" )
565+ }
566+
567+ // exactFallbackPricedModel returns an embedded fallback model pattern with
568+ // non-wildcard name and nonzero input/output rates, so lookups resolve
569+ // deterministically regardless of snapshot contents.
570+ func exactFallbackPricedModel (t * testing.T ) string {
571+ t .Helper ()
572+ for _ , p := range pricing .FallbackPricing () {
573+ if strings .ContainsAny (p .ModelPattern , "*/_" ) {
574+ continue
575+ }
576+ if p .InputPerMTok > 0 && p .OutputPerMTok > 0 {
577+ return p .ModelPattern
578+ }
579+ }
580+ t .Fatal ("no exact fallback-priced model in embedded snapshot" )
581+ return ""
582+ }
583+
508584func executeExportSessionsCommand (
509585 root * cobra.Command , args ... string ,
510586) (string , string , error ) {
0 commit comments