Skip to content

Commit 2b0759b

Browse files
authored
Merge pull request #1347 from wakatime/misc/more-ai-tests
More AI tests
2 parents 24f24fc + 7055a9e commit 2b0759b

File tree

1 file changed

+176
-0
lines changed

1 file changed

+176
-0
lines changed

pkg/ai/ai_test.go

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,182 @@ func TestWithAISync_ProducesExpectedHeartbeats(t *testing.T) {
733733
assert.Equal(t, "019d9353-333c-7c41-a909-26f5c6221a5a", h.AISession)
734734
}
735735

736+
func TestWithAISync_PreservesCopilotTokensAfterMergingAppHeartbeat(t *testing.T) {
737+
home := t.TempDir()
738+
t.Setenv("HOME", home)
739+
t.Setenv("USERPROFILE", home)
740+
741+
workspaceDir := filepath.Join(home, "Library", "Application Support", "Code", "User", "workspaceStorage", "workspace-1")
742+
require.NoError(t, os.MkdirAll(filepath.Join(workspaceDir, "chatSessions"), 0o755))
743+
require.NoError(t, os.MkdirAll(filepath.Join(workspaceDir, "chatEditingSessions", "session-1"), 0o755))
744+
745+
mainFile := filepath.Join(home, "project", "main.go")
746+
secondFile := filepath.Join(home, "project", "util.go")
747+
748+
require.NoError(t, os.MkdirAll(filepath.Dir(mainFile), 0o755))
749+
require.NoError(t, os.WriteFile(mainFile, []byte("package main\n"), 0o644))
750+
require.NoError(t, os.WriteFile(secondFile, []byte("package main\nfunc util() {}\n"), 0o644))
751+
752+
sessionPath := filepath.Join(workspaceDir, "chatSessions", "session-1.json")
753+
session := map[string]any{
754+
"version": 3,
755+
"creationDate": int64(1770000000000),
756+
"lastMessageDate": int64(1770000100000),
757+
"sessionId": "session-1",
758+
"requests": []any{
759+
map[string]any{
760+
"requestId": "request-1",
761+
"timestamp": int64(1770000001000),
762+
"agent": map[string]any{
763+
"extensionVersion": "0.42.3",
764+
},
765+
"message": map[string]any{
766+
"text": "Update the file and explain what changed",
767+
},
768+
"result": map[string]any{
769+
"metadata": map[string]any{
770+
"promptTokens": 9,
771+
"outputTokens": 4,
772+
},
773+
},
774+
"modelState": map[string]any{
775+
"value": 1,
776+
"completedAt": int64(1770000009000),
777+
},
778+
"variableData": map[string]any{
779+
"variables": []any{
780+
map[string]any{
781+
"kind": "file",
782+
"id": "file://" + strings.ReplaceAll(mainFile, " ", "%20"),
783+
"value": map[string]any{
784+
"fsPath": mainFile,
785+
},
786+
},
787+
},
788+
},
789+
"response": []any{
790+
map[string]any{
791+
"kind": "toolInvocationSerialized",
792+
"invocationMessage": map[string]any{
793+
"value": "Reading files",
794+
"uris": map[string]any{
795+
"file://" + strings.ReplaceAll(secondFile, " ", "%20"): map[string]any{
796+
"fsPath": secondFile,
797+
},
798+
},
799+
},
800+
},
801+
},
802+
},
803+
},
804+
}
805+
data, err := json.Marshal(session)
806+
require.NoError(t, err)
807+
require.NoError(t, os.WriteFile(sessionPath, data, 0o644))
808+
809+
statePath := filepath.Join(workspaceDir, "chatEditingSessions", "session-1", "state.json")
810+
state := map[string]any{
811+
"version": 2,
812+
"timeline": map[string]any{
813+
"fileBaselines": []any{
814+
[]any{
815+
"file://" + mainFile + "::request-1",
816+
map[string]any{
817+
"content": "package main\n",
818+
},
819+
},
820+
},
821+
"operations": []any{
822+
map[string]any{
823+
"type": "textEdit",
824+
"requestId": "request-1",
825+
"uri": map[string]any{
826+
"fsPath": mainFile,
827+
},
828+
"epoch": 1,
829+
"edits": []any{
830+
map[string]any{
831+
"text": "package main\n\nfunc main() {}\n",
832+
"range": map[string]any{
833+
"startLineNumber": 1,
834+
"startColumn": 1,
835+
"endLineNumber": 2,
836+
"endColumn": 1,
837+
},
838+
},
839+
},
840+
},
841+
},
842+
},
843+
}
844+
data, err = json.Marshal(state)
845+
require.NoError(t, err)
846+
require.NoError(t, os.WriteFile(statePath, data, 0o644))
847+
848+
after := time.Unix(1769999990, 0)
849+
tmpInternal, err := os.CreateTemp(t.TempDir(), "wakatime-internal")
850+
require.NoError(t, err)
851+
852+
defer tmpInternal.Close()
853+
854+
v := viper.New()
855+
v.Set("internal-config", tmpInternal.Name())
856+
v.Set("internal.ai_heartbeats_last_parsed_at", after.Format(ini.DateFormat))
857+
858+
handle := ai.WithAISync(ai.Config{
859+
Plugin: "editor/1.0.0",
860+
V: v,
861+
})(func(_ context.Context, hh []heartbeat.Heartbeat) ([]heartbeat.Result, error) {
862+
results := make([]heartbeat.Result, len(hh))
863+
for i := range hh {
864+
results[i] = heartbeat.Result{Heartbeat: hh[i]}
865+
}
866+
867+
return results, nil
868+
})
869+
870+
results, err := handle(t.Context(), []heartbeat.Heartbeat{})
871+
require.NoError(t, err)
872+
require.Len(t, results, 4)
873+
874+
var (
875+
mergedRead heartbeat.Heartbeat
876+
writeEdit heartbeat.Heartbeat
877+
foundRead bool
878+
foundEdit bool
879+
)
880+
881+
for _, result := range results {
882+
h := result.Heartbeat
883+
if h.Entity == mainFile && h.IsWrite != nil && !*h.IsWrite && h.AIPromptLength > 0 {
884+
mergedRead = h
885+
foundRead = true
886+
}
887+
888+
if h.Entity == mainFile && h.IsWrite != nil && *h.IsWrite {
889+
writeEdit = h
890+
foundEdit = true
891+
}
892+
}
893+
894+
require.True(t, foundRead)
895+
assert.Equal(t, heartbeat.FileType, mergedRead.EntityType)
896+
assert.Equal(t, int64(9), mergedRead.AIInputTokens)
897+
assert.Equal(t, int64(4), mergedRead.AIOutputTokens)
898+
assert.Equal(t, len([]rune("Update the file and explain what changed")), mergedRead.AIPromptLength)
899+
assert.Equal(t, filepath.Dir(mainFile), mergedRead.ProjectPathOverride)
900+
assert.Equal(t, "session-1", mergedRead.AISession)
901+
902+
require.True(t, foundEdit)
903+
require.NotNil(t, writeEdit.AILineChanges)
904+
assert.Equal(t, 2, *writeEdit.AILineChanges)
905+
assert.Zero(t, writeEdit.AIInputTokens)
906+
assert.Zero(t, writeEdit.AIOutputTokens)
907+
assert.Zero(t, writeEdit.AIPromptLength)
908+
assert.Equal(t, filepath.Dir(mainFile), writeEdit.ProjectPathOverride)
909+
assert.Equal(t, "session-1", writeEdit.AISession)
910+
}
911+
736912
func resetSingleton(t *testing.T) {
737913
t.Helper()
738914

0 commit comments

Comments
 (0)