File size: 5,958 Bytes
cd8bd0a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
import { describe, it, before } from "node:test";
import assert from "node:assert";
import { MimocodeExecutor, generateFingerprint } from "../../open-sse/executors/mimocode.ts";

const PROXY_URL = process.env.MIMOCODE_SOCKS5_PROXY;

function parseProxyUrl(url: string): { type: string; host: string; port: number } | null {
  try {
    const parsed = new URL(url);
    return { type: parsed.protocol.replace(":", ""), host: parsed.hostname, port: parsed.port ? Number(parsed.port) : 1080 };
  } catch {
    return null;
  }
}

function requireProxy() {
  if (!PROXY_URL) {
    return false;
  }
  const parsed = parseProxyUrl(PROXY_URL);
  return parsed !== null;
}

const proxyConfig = PROXY_URL ? parseProxyUrl(PROXY_URL) : null;

describe("mimocode per-account proxy — SOCKS5 integration", { timeout: 30_000 }, () => {
  before(() => {
    if (!PROXY_URL) {
      console.log("# MIMOCODE_SOCKS5_PROXY not set, skipping live proxy tests");
    }
  });

  it("bootstrap returns JWT through configured proxy", { skip: !requireProxy() ? "MIMOCODE_SOCKS5_PROXY not set" : false }, async () => {
    process.env.ENABLE_SOCKS5_PROXY = "true";
    const { Socks5ProxyAgent } = await import("undici");
    const agent = new Socks5ProxyAgent(PROXY_URL!);

    const fp = generateFingerprint("integration-bootstrap-" + Date.now());
    const resp = await fetch("https://api.xiaomimimo.com/api/free-ai/bootstrap", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ client: fp }),
      // @ts-expect-error — undici dispatcher
      dispatcher: agent,
      signal: AbortSignal.timeout(15_000),
    });
    assert.strictEqual(resp.status, 200, `Bootstrap through proxy: expected 200, got ${resp.status}`);
    const data = await resp.json();
    assert.ok(data.jwt, "Response should contain JWT");
    assert.ok(typeof data.jwt === "string" && data.jwt.length > 10, "JWT should be a non-trivial string");
  });

  it("chat request succeeds through configured proxy", { skip: !requireProxy() ? "MIMOCODE_SOCKS5_PROXY not set" : false }, async () => {
    process.env.ENABLE_SOCKS5_PROXY = "true";
    const { Socks5ProxyAgent } = await import("undici");
    const agent = new Socks5ProxyAgent(PROXY_URL!);

    const fp = generateFingerprint("integration-chat-" + Date.now());
    const bootstrapResp = await fetch("https://api.xiaomimimo.com/api/free-ai/bootstrap", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ client: fp }),
      // @ts-expect-error — undici dispatcher
      dispatcher: agent,
      signal: AbortSignal.timeout(15_000),
    });
    assert.strictEqual(bootstrapResp.status, 200);
    const { jwt } = await bootstrapResp.json();

    const chatResp = await fetch("https://api.xiaomimimo.com/api/free-ai/openai/chat", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "Authorization": `Bearer ${jwt}`,
        "X-Mimo-Source": "mimocode-cli-free",
      },
      body: JSON.stringify({
        model: "mimo-auto",
        messages: [
          { role: "system", content: "You are MiMoCode, an interactive CLI tool that helps users with software engineering tasks." },
          { role: "user", content: "Say exactly: proxy-integration-ok" },
        ],
        stream: false,
      }),
      // @ts-expect-error — undici dispatcher
      dispatcher: agent,
      signal: AbortSignal.timeout(20_000),
    });
    assert.ok(chatResp.status === 200 || chatResp.status === 429,
      `Chat through proxy: expected 200/429, got ${chatResp.status}`);
  });

  it("accounts carry proxy config after sync", () => {
    const exec = new MimocodeExecutor();
    const fp = "integration-fp-1";
    const cfg = proxyConfig || { type: "socks5", host: "127.0.0.1", port: 1080 };
    (exec as any).accounts = [
      { fingerprint: fp, jwt: "", expiresAt: 0, cooldownUntil: 0, consecutiveFails: 0, proxy: null },
    ];
    (exec as any).nextAccountIdx = 0;

    (exec as any).syncAccountsFromCredentials({
      providerSpecificData: {
        accountProxies: [{ fingerprint: fp, proxy: cfg }],
      },
    });

    const acct = (exec as any).accounts.find((a: any) => a.fingerprint === fp);
    assert.ok(acct, "Account should exist");
    assert.deepStrictEqual(acct.proxy, cfg);
  });

  it("two accounts with different proxies tracked independently", () => {
    const exec = new MimocodeExecutor();
    const fp1 = "integration-fp-a";
    const fp2 = "integration-fp-b";
    const proxy1 = { type: "http" as const, host: "proxy-a.example.com", port: 8080 };
    const proxy2 = { type: "socks5" as const, host: "proxy-b.example.com", port: 1080 };

    (exec as any).accounts = [
      { fingerprint: fp1, jwt: "", expiresAt: 0, cooldownUntil: 0, consecutiveFails: 0, proxy: null },
      { fingerprint: fp2, jwt: "", expiresAt: 0, cooldownUntil: 0, consecutiveFails: 0, proxy: null },
    ];
    (exec as any).nextAccountIdx = 0;

    (exec as any).syncAccountsFromCredentials({
      providerSpecificData: {
        accountProxies: [
          { fingerprint: fp1, proxy: proxy1 },
          { fingerprint: fp2, proxy: proxy2 },
        ],
      },
    });

    const a1 = (exec as any).accounts.find((a: any) => a.fingerprint === fp1);
    const a2 = (exec as any).accounts.find((a: any) => a.fingerprint === fp2);
    assert.deepStrictEqual(a1.proxy, proxy1, "Account 1 should have proxy1");
    assert.deepStrictEqual(a2.proxy, proxy2, "Account 2 should have proxy2");
    assert.notDeepStrictEqual(a1.proxy, a2.proxy, "Proxies should differ");
  });

  it("no accountProxies keeps all proxies null (backward compat)", () => {
    const exec = new MimocodeExecutor();
    const accounts = (exec as any).accounts;
    assert.ok(accounts.length >= 1);
    for (const acct of accounts) {
      assert.strictEqual(acct.proxy, null, "Default account proxy should be null");
    }
  });
});