@@ -6783,92 +6783,107 @@ describe("sendJobConclusionSpan does not emit OTLP metrics", () => {
67836783
67846784describe ( "parseAICreditsFromUsageJsonl" , ( ) => {
67856785 let readFileSpy ;
6786+ let existsSyncSpy ;
67866787
67876788 beforeEach ( ( ) => {
67886789 readFileSpy = vi . spyOn ( fs , "readFileSync" ) . mockImplementation ( ( ) => {
67896790 throw Object . assign ( new Error ( "ENOENT" ) , { code : "ENOENT" } ) ;
67906791 } ) ;
6792+ existsSyncSpy = vi . spyOn ( fs , "existsSync" ) . mockReturnValue ( false ) ;
67916793 } ) ;
67926794
67936795 afterEach ( ( ) => {
67946796 readFileSpy . mockRestore ( ) ;
6797+ existsSyncSpy . mockRestore ( ) ;
67956798 } ) ;
67966799
67976800 it ( "returns 0 when the file does not exist" , ( ) => {
67986801 expect ( parseAICreditsFromUsageJsonl ( "/tmp/gh-aw/agent_usage.jsonl" ) ) . toBe ( 0 ) ;
67996802 } ) ;
68006803
68016804 it ( "returns 0 for an empty file" , ( ) => {
6805+ existsSyncSpy . mockReturnValue ( true ) ;
68026806 readFileSpy . mockImplementation ( ( ) => "" ) ;
68036807 expect ( parseAICreditsFromUsageJsonl ( "/tmp/gh-aw/agent_usage.jsonl" ) ) . toBe ( 0 ) ;
68046808 } ) ;
68056809
68066810 it ( "returns 0 for a file with only whitespace" , ( ) => {
6811+ existsSyncSpy . mockReturnValue ( true ) ;
68076812 readFileSpy . mockImplementation ( ( ) => " \n \n" ) ;
68086813 expect ( parseAICreditsFromUsageJsonl ( "/tmp/gh-aw/agent_usage.jsonl" ) ) . toBe ( 0 ) ;
68096814 } ) ;
68106815
68116816 it ( "sums ai_credits from a single-entry file" , ( ) => {
6817+ existsSyncSpy . mockReturnValue ( true ) ;
68126818 readFileSpy . mockImplementation ( ( ) => JSON . stringify ( { ai_credits : 1.5 , model : "gpt-4o" } ) + "\n" ) ;
68136819 expect ( parseAICreditsFromUsageJsonl ( "/tmp/gh-aw/agent_usage.jsonl" ) ) . toBe ( 1.5 ) ;
68146820 } ) ;
68156821
68166822 it ( "sums ai_credits across multiple entries" , ( ) => {
6823+ existsSyncSpy . mockReturnValue ( true ) ;
68176824 const lines = [ JSON . stringify ( { ai_credits : 1.0 , model : "gpt-4o" } ) , JSON . stringify ( { ai_credits : 0.5 , model : "claude-3-5-sonnet" } ) , JSON . stringify ( { ai_credits : 0.25 } ) ] . join ( "\n" ) ;
68186825 readFileSpy . mockImplementation ( ( ) => lines ) ;
68196826 expect ( parseAICreditsFromUsageJsonl ( "/tmp/gh-aw/agent_usage.jsonl" ) ) . toBe ( 1.75 ) ;
68206827 } ) ;
68216828
68226829 it ( "supports camelCase aiCredits field" , ( ) => {
6830+ existsSyncSpy . mockReturnValue ( true ) ;
68236831 readFileSpy . mockImplementation ( ( ) => JSON . stringify ( { aiCredits : 2.0 } ) + "\n" ) ;
68246832 expect ( parseAICreditsFromUsageJsonl ( "/tmp/gh-aw/agent_usage.jsonl" ) ) . toBe ( 2.0 ) ;
68256833 } ) ;
68266834
68276835 it ( "prefers snake_case ai_credits over camelCase aiCredits when both are present" , ( ) => {
6836+ existsSyncSpy . mockReturnValue ( true ) ;
68286837 readFileSpy . mockImplementation ( ( ) => JSON . stringify ( { ai_credits : 1.0 , aiCredits : 9.0 } ) + "\n" ) ;
68296838 expect ( parseAICreditsFromUsageJsonl ( "/tmp/gh-aw/agent_usage.jsonl" ) ) . toBe ( 1.0 ) ;
68306839 } ) ;
68316840
68326841 it ( "skips entries without an ai_credits or aiCredits field" , ( ) => {
6842+ existsSyncSpy . mockReturnValue ( true ) ;
68336843 const lines = [ JSON . stringify ( { model : "gpt-4o" , input_tokens : 100 } ) , JSON . stringify ( { ai_credits : 0.75 } ) ] . join ( "\n" ) ;
68346844 readFileSpy . mockImplementation ( ( ) => lines ) ;
68356845 expect ( parseAICreditsFromUsageJsonl ( "/tmp/gh-aw/agent_usage.jsonl" ) ) . toBe ( 0.75 ) ;
68366846 } ) ;
68376847
68386848 it ( "skips malformed JSON lines without throwing" , ( ) => {
6849+ existsSyncSpy . mockReturnValue ( true ) ;
68396850 const lines = [ "{not valid json}" , JSON . stringify ( { ai_credits : 1.0 } ) , "also bad" ] . join ( "\n" ) ;
68406851 readFileSpy . mockImplementation ( ( ) => lines ) ;
68416852 expect ( parseAICreditsFromUsageJsonl ( "/tmp/gh-aw/agent_usage.jsonl" ) ) . toBe ( 1.0 ) ;
68426853 } ) ;
68436854
68446855 it ( "skips entries with negative ai_credits" , ( ) => {
6856+ existsSyncSpy . mockReturnValue ( true ) ;
68456857 const lines = [ JSON . stringify ( { ai_credits : - 0.5 } ) , JSON . stringify ( { ai_credits : 1.0 } ) ] . join ( "\n" ) ;
68466858 readFileSpy . mockImplementation ( ( ) => lines ) ;
68476859 expect ( parseAICreditsFromUsageJsonl ( "/tmp/gh-aw/agent_usage.jsonl" ) ) . toBe ( 1.0 ) ;
68486860 } ) ;
68496861
68506862 it ( "skips entries where ai_credits is a non-numeric string" , ( ) => {
6863+ existsSyncSpy . mockReturnValue ( true ) ;
68516864 const lines = [ JSON . stringify ( { ai_credits : "invalid" } ) , JSON . stringify ( { ai_credits : 0.5 } ) ] . join ( "\n" ) ;
68526865 readFileSpy . mockImplementation ( ( ) => lines ) ;
68536866 expect ( parseAICreditsFromUsageJsonl ( "/tmp/gh-aw/agent_usage.jsonl" ) ) . toBe ( 0.5 ) ;
68546867 } ) ;
68556868
68566869 it ( "accepts ai_credits expressed as a numeric string" , ( ) => {
6870+ existsSyncSpy . mockReturnValue ( true ) ;
68576871 readFileSpy . mockImplementation ( ( ) => JSON . stringify ( { ai_credits : "1.234" } ) + "\n" ) ;
68586872 expect ( parseAICreditsFromUsageJsonl ( "/tmp/gh-aw/agent_usage.jsonl" ) ) . toBeCloseTo ( 1.234 ) ;
68596873 } ) ;
68606874
68616875 it ( "accepts ai_credits of zero" , ( ) => {
6876+ existsSyncSpy . mockReturnValue ( true ) ;
68626877 readFileSpy . mockImplementation ( ( ) => JSON . stringify ( { ai_credits : 0 } ) + "\n" ) ;
68636878 expect ( parseAICreditsFromUsageJsonl ( "/tmp/gh-aw/agent_usage.jsonl" ) ) . toBe ( 0 ) ;
68646879 } ) ;
68656880
68666881 it ( "uses the AGENTS_USAGE_JSONL_PATH constant for the agents file" , ( ) => {
6867- expect ( AGENTS_USAGE_JSONL_PATH ) . toBe ( "/tmp/gh-aw/agent_usage .jsonl" ) ;
6882+ expect ( AGENTS_USAGE_JSONL_PATH ) . toBe ( "/tmp/gh-aw/usage/agent/token_usage .jsonl" ) ;
68686883 } ) ;
68696884
68706885 it ( "uses the DETECTION_USAGE_JSONL_PATH constant for the detection file" , ( ) => {
6871- expect ( DETECTION_USAGE_JSONL_PATH ) . toBe ( "/tmp/gh-aw/detection_usage .jsonl" ) ;
6886+ expect ( DETECTION_USAGE_JSONL_PATH ) . toBe ( "/tmp/gh-aw/usage/detection/token_usage .jsonl" ) ;
68726887 } ) ;
68736888} ) ;
68746889
@@ -6880,7 +6895,7 @@ describe("sendJobConclusionSpan conclusion job AI credits from usage files", ()
68806895 /** @type {Record<string, string | undefined> } */
68816896 const savedEnv = { } ;
68826897 const envKeys = [ "GH_AW_OTLP_ENDPOINTS" , "INPUT_JOB_NAME" , "GH_AW_AIC" , "GH_AW_AGENT_CONCLUSION" , "GITHUB_RUN_ID" , "GITHUB_ACTOR" , "GITHUB_REPOSITORY" ] ;
6883- let mkdirSpy , appendSpy , readFileSpy ;
6898+ let mkdirSpy , appendSpy , readFileSpy , existsSyncSpy ;
68846899
68856900 beforeEach ( ( ) => {
68866901 vi . stubGlobal ( "fetch" , vi . fn ( ) . mockResolvedValue ( { ok : true , status : 200 , statusText : "OK" } ) ) ;
@@ -6890,6 +6905,10 @@ describe("sendJobConclusionSpan conclusion job AI credits from usage files", ()
68906905 }
68916906 mkdirSpy = vi . spyOn ( fs , "mkdirSync" ) . mockImplementation ( ( ) => { } ) ;
68926907 appendSpy = vi . spyOn ( fs , "appendFileSync" ) . mockImplementation ( ( ) => { } ) ;
6908+ existsSyncSpy = vi . spyOn ( fs , "existsSync" ) . mockImplementation ( filePath => {
6909+ // Return true for paths that will be read by readFileSpy
6910+ return filePath === AGENTS_USAGE_JSONL_PATH || filePath === DETECTION_USAGE_JSONL_PATH ;
6911+ } ) ;
68936912 readFileSpy = vi . spyOn ( fs , "readFileSync" ) . mockImplementation ( ( ) => {
68946913 throw Object . assign ( new Error ( "ENOENT" ) , { code : "ENOENT" } ) ;
68956914 } ) ;
@@ -6909,6 +6928,7 @@ describe("sendJobConclusionSpan conclusion job AI credits from usage files", ()
69096928 mkdirSpy . mockRestore ( ) ;
69106929 appendSpy . mockRestore ( ) ;
69116930 readFileSpy . mockRestore ( ) ;
6931+ existsSyncSpy . mockRestore ( ) ;
69126932 } ) ;
69136933
69146934 /**
@@ -7004,7 +7024,7 @@ describe("sendJobConclusionSpan conclusion job AI credits from usage files", ()
70047024 it ( "reads usage files from the fixed /tmp/gh-aw/ paths regardless of GH_AW_AGENT_OUTPUT" , async ( ) => {
70057025 process . env . GH_AW_AGENT_OUTPUT = "/custom/path/output.json" ;
70067026 readFileSpy . mockImplementation ( filePath => {
7007- if ( filePath === "/tmp/gh-aw/agent_usage.jsonl" ) return JSON . stringify ( { ai_credits : 2.0 } ) + "\n" ;
7027+ if ( filePath === AGENTS_USAGE_JSONL_PATH ) return JSON . stringify ( { ai_credits : 2.0 } ) + "\n" ;
70087028 throw Object . assign ( new Error ( "ENOENT" ) , { code : "ENOENT" } ) ;
70097029 } ) ;
70107030
0 commit comments