first commit

This commit is contained in:
Beyhan Oğur
2026-04-26 21:52:23 +03:00
commit 880f412e2c
2662 changed files with 866266 additions and 0 deletions

View File

@@ -0,0 +1,180 @@
package async
import (
"encoding/json"
"net/http"
"testing"
)
// TestLifecycle_AllEndpoints_ReachesTerminalState submits a job for every supported
// endpoint and polls until it reaches completed or failed, then validates the
// terminal response shape. Passes for either outcome — the test asserts the async
// mechanism itself, not model availability.
// Runs in both global and VK modes.
func TestLifecycle_AllEndpoints_ReachesTerminalState(t *testing.T) {
for _, mode := range testModes() {
t.Run(mode.name, func(t *testing.T) {
for _, ec := range endpointCases() {
t.Run(ec.name, func(t *testing.T) {
_, submitted, body := submitCase(t, ec, mode.headers)
if submitted.ID == "" {
t.Fatalf("submit returned no job id: %s", body)
}
pollPath := jobPollPath(ec.pollBase, submitted.ID)
code, job := pollUntilTerminal(t, pollPath, mode.headers)
if code != http.StatusOK {
t.Errorf("expected 200 for terminal job, got %d", code)
}
if job.ID != submitted.ID {
t.Errorf("polled id %q does not match submitted id %q", job.ID, submitted.ID)
}
if job.CompletedAt == nil {
t.Error("completed_at must be set on a terminal job")
}
if job.ExpiresAt == nil {
t.Error("expires_at must be set on a terminal job")
}
if job.CompletedAt != nil && job.ExpiresAt != nil && !job.ExpiresAt.After(*job.CompletedAt) {
t.Error("expires_at must be after completed_at")
}
switch job.Status {
case "completed":
if len(job.Result) == 0 || string(job.Result) == "null" {
t.Error("completed job must have a non-null result")
}
case "failed":
if len(job.Error) == 0 || string(job.Error) == "null" {
t.Error("failed job must have a non-null error")
}
if job.StatusCode == 0 {
t.Error("failed job must carry a non-zero status_code")
}
}
})
}
})
}
}
// TestLifecycle_Poll_NonExistentJob_Returns404 confirms that polling a random job ID
// returns 404 regardless of VK mode (job lookup fails before VK check).
// Uses chat_completions as a representative endpoint — all endpoints share the same
// RetrieveJob() path, so repeating across all 11 adds no coverage.
func TestLifecycle_Poll_NonExistentJob_Returns404(t *testing.T) {
const fakeID = "00000000-0000-0000-0000-000000000000"
ec := chatCompletionCase()
for _, mode := range testModes() {
t.Run(mode.name, func(t *testing.T) {
pollPath := jobPollPath(ec.pollBase, fakeID)
code, _, _ := pollOnce(t, pollPath, mode.headers)
if code != http.StatusNotFound {
t.Errorf("expected 404 for non-existent job, got %d", code)
}
})
}
}
// TestLifecycle_CompletedJobResultShape checks that completed jobs carry the expected
// top-level fields in their result JSON. If a job fails (e.g., no live API key), the
// shape check is skipped for that case — the test asserts structure, not model availability.
func TestLifecycle_CompletedJobResultShape(t *testing.T) {
type shapeCheck struct {
name string
check func(t *testing.T, result json.RawMessage)
}
shapeChecks := map[string]shapeCheck{
"chat_completions": {
"choices[]",
func(t *testing.T, result json.RawMessage) {
var r struct {
Choices []json.RawMessage `json:"choices"`
}
if err := json.Unmarshal(result, &r); err != nil {
t.Fatalf("unmarshal choices: %v", err)
}
if len(r.Choices) == 0 {
t.Error("completed chat job must have at least one choice")
}
},
},
"embeddings": {
"data[]",
func(t *testing.T, result json.RawMessage) {
var r struct {
Data []json.RawMessage `json:"data"`
}
if err := json.Unmarshal(result, &r); err != nil {
t.Fatalf("unmarshal data: %v", err)
}
if len(r.Data) == 0 {
t.Error("completed embeddings job must have at least one data entry")
}
},
},
"rerank": {
"results[]",
func(t *testing.T, result json.RawMessage) {
var r struct {
Results []json.RawMessage `json:"results"`
}
if err := json.Unmarshal(result, &r); err != nil {
t.Fatalf("unmarshal results: %v", err)
}
if len(r.Results) == 0 {
t.Error("completed rerank job must have at least one result")
}
},
},
}
for _, ec := range endpointCases() {
sc, ok := shapeChecks[ec.name]
if !ok {
continue
}
t.Run(ec.name+"/"+sc.name, func(t *testing.T) {
_, submitted, body := submitCase(t, ec, nil)
if submitted.ID == "" {
t.Fatalf("submit returned no job id: %s", body)
}
pollPath := jobPollPath(ec.pollBase, submitted.ID)
_, job := pollUntilTerminal(t, pollPath, nil)
if job.Status != "completed" {
t.Skipf("job status=%q (not completed) — shape check skipped", job.Status)
}
sc.check(t, job.Result)
})
}
}
// TestLifecycle_Poll_WrongEndpointType_Returns404 submits a job on one endpoint and
// polls it via a different endpoint's path, expecting 404 (type mismatch).
func TestLifecycle_Poll_WrongEndpointType_Returns404(t *testing.T) {
cases := endpointCases()
if len(cases) < 2 {
t.Skip("need at least two endpoint cases")
}
for _, mode := range testModes() {
t.Run(mode.name, func(t *testing.T) {
// Submit on cases[0], poll via cases[1]'s poll base.
submitter := cases[0]
wrongBase := cases[1].pollBase
_, submitted, body := submitCase(t, submitter, mode.headers)
if submitted.ID == "" {
t.Fatalf("submit returned no job id: %s", body)
}
pollPath := jobPollPath(wrongBase, submitted.ID)
code, _, _ := pollOnce(t, pollPath, mode.headers)
if code != http.StatusNotFound {
t.Errorf("expected 404 when polling with wrong endpoint type, got %d", code)
}
})
}
}