Repository navigation
Expand file tree
/
Copy path_test_qoder.py
More file actions
5088 lines (4576 loc) · 247 KB
/
Copy path_test_qoder.py
File metadata and controls
5088 lines (4576 loc) · 247 KB
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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"""Deterministic offline tests for the Qoder gateway.
No network: verifies the crypto primitives (AES FIPS vectors, RSA padding
structure, Qoder custom base64 round-trip), COSY signature layout, request
body construction, SSE envelope unwrapping, Responses-API custom-tool
translation, and check-in response normalization.
python _test_qoder.py
外部 fixture(可选):[4.5] 组的官方加解密 KAT 需要协议 fixture 目录
(内含 credential.json 与 model-cache.json)。目录按以下顺序自动探测:
1) 环境变量 QD_TEST_FIXTURE_DIR
2) <仓库>/testdata/protocol/1.1.34
3) <仓库>/tests/fixtures/protocol/1.1.34
4) <仓库>/../qoder-ref/cli2api/testdata/protocol/1.1.34
5) %TEMP%/qoder-ref/cli2api/testdata/protocol/1.1.34
6) ~/qoder-ref/cli2api/testdata/protocol/1.1.34
缺 fixture 时依赖它的 3 条断言打印 [SKIP](不计失败),**绝不静默**:
[SKIP] 行、候选清单、末行汇总都会报出跳过数量。同组的 AES-256
密钥表 / 互逆 KAT 不依赖 fixture,永远执行。
退出码:0 = 无 FAIL(允许存在 SKIP);1 = 存在 FAIL。
"""
import hashlib
import json
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
os.environ.setdefault("ACCOUNTS_DIR",
os.path.join(os.path.dirname(os.path.abspath(__file__)),
"_acc"))
os.environ.setdefault("USAGE_DIR",
os.path.join(os.path.dirname(os.path.abspath(__file__)),
"_use"))
# 离线测试不拉起客户端原生二进制(活动平台的机器身份桥);测试里显式关闭
os.environ.setdefault("QD_NATIVE_IDENTITY", "0")
import qoder_proxy as P
# ---- 指纹行(与前端六个 _test_dashboard_*.js 同格式;复用 tests/_suite_head.py 的实现)----
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "tests"))
from _suite_head import declare_suite # noqa: E402
declare_suite(__file__, ["qoder_proxy.py", "qoder_accounts.py", "qoder_sign.py"])
import qoder_sign as S
import qoder_catalog as C
import qoder_accounts as A
import qoder_tasks as T
PASS = FAIL = SKIP = 0
def check(label, cond, extra=""):
global PASS, FAIL
if cond:
PASS += 1
print(" [PASS] " + label)
else:
FAIL += 1
print(" [FAIL] " + label + (" " + str(extra) if extra else ""))
def skip(label, reason=""):
"""显式跳过(缺外部 fixture / 缺环境),绝不静默。
SKIP 不改变退出码(退出码只看 FAIL),但会打印醒目行并计入末行汇总,
所以"这组没跑"永远可见;补齐 fixture 后必须重跑到它真的 PASS。
"""
global SKIP
SKIP += 1
print(" [SKIP] " + label + ((" -- " + str(reason)) if reason else ""))
print("[1] Qoder custom base64 variant")
enc = S.qoder_encode(b"{}")
check("encode({}) deterministic", enc == S.qoder_encode(b"{}"))
check("decode(encode(x)) == x", S.qoder_decode(enc) == b"{}")
sample = json.dumps({"b": 1, "a": "中文测试", "c": [1, 2, 3]}).encode()
check("roundtrip with unicode/json", S.qoder_decode(S.qoder_encode(sample)) == sample)
check("output uses only custom alphabet",
all(c in S.QODER_CUSTOM_ALPHABET or c == S.QODER_PAD for c in enc))
check("padding is $", S.qoder_encode(b"a" * 100).count("$") >= 0 and "=" not in enc)
print()
print("[2] AES-128 (FIPS-197 / NIST vectors)")
k = bytes.fromhex("000102030405060708090a0b0c0d0e0f")
pt = bytes.fromhex("00112233445566778899aabbccddeeff")
ct = S._encrypt_block(pt, S._expand_key(k)).hex()
check("FIPS-197 C.1 block", ct == "69c4e0d86a7b0430d8cdb78070b4c55a", ct)
k2 = bytes.fromhex("2b7e151628aed2a6abf7158809cf4f3c")
iv = bytes.fromhex("000102030405060708090a0b0c0d0e0f")
pt2 = bytes.fromhex("6bc1bee22e409f96e93d7e117393172a")
xored = bytes(a ^ b for a, b in zip(pt2, iv))
c2 = S._encrypt_block(xored, S._expand_key(k2)).hex()
check("SP800-38A CBC first block", c2 == "7649abac8119b246cee98e9b12e9197d", c2)
# CBC chaining through aes_cbc_encrypt (key==iv style input, PKCS7)
blob = S.aes_cbc_encrypt(b"hello qoder", b"0123456789abcdef", b"0123456789abcdef")
check("aes_cbc_encrypt block-aligned", len(blob) % 16 == 0 and len(blob) >= 16)
# 11 字节明文 -> PKCS7 补 5 -> 一个密文块
check("pkcs7 grows 11B input to one 16B block", len(blob) == 16, len(blob))
check("16B input grows to two blocks",
len(S.aes_cbc_encrypt(b"0123456789abcdef", b"0123456789abcdef",
b"0123456789abcdef")) == 32)
print()
print("[3] RSA PKCS#1 v1.5 public encryption")
import base64 as _b64m
_der = _b64m.b64decode("".join(l for l in S.SERVER_PUB_PEM.splitlines()
if "BEGIN" not in l and "END" not in l))
check("PEM parses to 1024-bit modulus", S._RSA_N.bit_length() == 1024,
S._RSA_N.bit_length())
check("DER carries 129-byte INTEGER", b"\x02\x81\x81" in _der)
check("exponent 65537", S._RSA_E == 65537)
ct_rsa = S.rsa_pkcs1v15_encrypt(b"0123456789abcdef")
check("ciphertext length = k = 128", len(ct_rsa) == 128, len(ct_rsa))
m_int = int.from_bytes(ct_rsa, "big")
check("ciphertext < n", m_int < S._RSA_N)
check("pkcs1v15 PS length formula",
len(S.rsa_pkcs1v15_encrypt(b"x")) == 128)
print()
print("[4] COSY session & bearer signature")
sess = S.CosySession(uid="test-uid-001", nickname="tester",
access_token="dt-abc", refresh_token="drt-xyz")
url = "https://gateway.qoder.com.cn" + P.CHAT_PATH
body_enc = S.qoder_encode(b'{"x":1}')
h = sess.headers(body_enc, url, model_key="qmodel", sse=True)
check("has full cosy header set",
all(kk in h for kk in ("authorization", "cosy-key", "cosy-user",
"cosy-machineid", "cosy-machinetoken",
"cosy-machinetype", "cosy-date", "cosy-version")))
check("x-model-key set", h.get("x-model-key") == "qmodel")
check("cache-control for sse", h.get("cache-control") == "no-cache")
check("bearer format COSY.payload.sig",
h["authorization"].startswith("Bearer COSY.")
and len(h["authorization"].split(".")) == 3)
parts = h["authorization"][len("Bearer "):].split(".")
payload_b64, sig = parts[1], parts[2]
path_stripped = "/api/v2/service/pro/sse/agent_chat_generation"
raw = payload_b64 + "\n" + sess.cosy_key + "\n" + h["cosy-date"] + "\n" \
+ body_enc + "\n" + path_stripped
check("md5 signature over body+path", sig == hashlib.md5(raw.encode()).hexdigest())
check("machine id stable per uid",
S.CosySession(uid="test-uid-001").machine_id == sess.machine_id)
check("machine ids differ across uid",
S.CosySession(uid="other-uid").machine_id != sess.machine_id)
import base64 as _b64
payload = json.loads(_b64.b64decode(payload_b64))
check("payload keys sorted-compact",
sorted(payload.keys()) == ["cosyVersion", "ideVersion", "info",
"requestId", "version"])
check("payload cosyVersion", payload["cosyVersion"] == S.COSY_VERSION)
print()
print("[5] model alias resolution (official keys)")
check("qwen3.8-max -> qmodel_38max",
C.resolve_upstream_key("qwen3.8-max") == "qmodel_38max")
check("old key qmodel_preview -> qmodel_38max",
C.resolve_upstream_key("qmodel_preview") == "qmodel_38max")
check("qwen3.8-flash -> qfmodel",
C.resolve_upstream_key("qwen3.8-flash") == "qfmodel")
check("deepseek-v4-pro -> dmodel",
C.resolve_upstream_key("deepseek-v4-pro") == "dmodel")
check("shared key passthrough",
C.resolve_upstream_key("qmodel") == "qmodel")
check("aliased qoder/ prefix",
C.resolve_upstream_key("qoder/qwen3.7-max") == "qmodel_latest")
check("unknown model passthrough",
C.resolve_upstream_key("mystery-model") == "mystery-model")
check("empty -> auto", C.resolve_upstream_key("") == "auto")
check("realm-aware: official key of that realm accepted",
C.resolve_upstream_key("q37fmodel", realm="cn") == "q37fmodel")
print()
print("[5.5] per-realm catalogs follow the official client (must DIFFER)")
intl_keys = [m["key"] for m in C.STATIC_INTL_MODELS]
cn_keys = [m["key"] for m in C.STATIC_CN_MODELS]
check("intl catalog not empty (>=17)", len(intl_keys) >= 17, len(intl_keys))
check("cn catalog not empty (14)", len(cn_keys) == 14, len(cn_keys))
check("two realms' catalogs differ", intl_keys != cn_keys)
check("intl-only: smodel present, absent from cn",
"smodel" in intl_keys and "smodel" not in cn_keys)
check("cn-only: q37fmodel present, absent from intl",
"q37fmodel" in cn_keys and "q37fmodel" not in intl_keys)
check("cn-only: gm51model present, absent from intl",
"gm51model" in cn_keys and "gm51model" not in intl_keys)
intl_en = {m["key"] for m in C.STATIC_INTL_MODELS if m.get("enable")}
cn_en = {m["key"] for m in C.STATIC_CN_MODELS if m.get("enable")}
check("intl enabled flags = {qmodel_38max, qfmodel} (official plan state)",
intl_en == {"qmodel_38max", "qfmodel"}, sorted(intl_en))
check("cn all 14 enabled", len(cn_en) == 14, sorted(cn_en))
# 全量列出(不按 enable 过滤)
merged_i = P.merge_catalog([], realm="intl")
merged_c = P.merge_catalog([], realm="cn")
check("merge keeps FULL intl list (17, no enable filtering)", len(merged_i) == 17,
len(merged_i))
check("merge keeps FULL cn list (14)", len(merged_c) == 14, len(merged_c))
# 清单以官方动态/本机目录为准(桌面版此刻显示什么就显示什么)
dyn_keys = [("cmodel", {"key": "cmodel", "display_name": "Cantus"})] # 反例占位
primary15 = [(m["key"], dict(m)) for m in C.STATIC_INTL_MODELS
if m["key"] not in ("cmodel", "smodel")] # 模拟动态返回的 15 条
merged_dyn = P.merge_catalog(primary15, realm="intl")
check("merge follows primary set (dynamic 15 wins, static-only excluded)",
len(merged_dyn) == 15 and
{k for k, _ in merged_dyn} == {m["key"] for m in C.STATIC_INTL_MODELS}
- {"cmodel", "smodel"}, len(merged_dyn))
print()
print("[5.7] official full-fidelity fields: display id / pricing / windows / efforts")
entry_i = next(m for m in C.STATIC_INTL_MODELS if m["key"] == "smodel")
check("intl exclusive entry retains full fields",
{"context_config", "thinking_config", "is_free", "is_new"} <= set(entry_i.keys()),
sorted(entry_i.keys()))
cn38 = next(m for m in C.STATIC_CN_MODELS if m["key"] == "qmodel_38max")
promo = cn38.get("promotion") or {}
# 注意:promo.active / price_factor 是**快照抓取时刻**的官方值(低谷时段抓的
# 快照 active=True 且 price=峰价×折扣;高峰时段抓的 active=False 且 price=峰价)。
# 断言官方不变量,而不是断言"抓取时正好在打折",否则测试随抓取时刻漂移。
check("cn qmodel_38max carries off-peak promotion metadata",
bool(promo) and promo.get("rule_id") == "idle_time_model_credit_discount"
and isinstance(promo.get("active"), bool), promo.get("rule_id"))
check("off-peak window = 22:00-08:00",
promo.get("window_start") == "22:00" and promo.get("window_end") == "08:00")
check("peak factor (before_promotion) = 0.5", promo.get("before_promotion_price_factor") == 0.5,
promo.get("before_promotion_price_factor"))
check("current price_factor is peak or valley (0.5 / 0.2)",
cn38.get("price_factor") in (0.5, 0.2), cn38.get("price_factor"))
check("price_factor matches promo.active (peak when inactive)",
(cn38.get("price_factor") == 0.2) is bool(promo.get("active")),
(cn38.get("price_factor"), promo.get("active")))
check("discount_factor = 0.4 (4折)", promo.get("discount_factor") == 0.4)
check("promotion badge/description localized",
bool((promo.get("badge") or {}).get("en")) and bool((promo.get("description") or {}).get("en")))
qf = next(m for m in C.STATIC_CN_MODELS if m["key"] == "qfmodel")
check("qfmodel free + original factor kept",
qf.get("is_free") is True and qf.get("original_price_factor") == 0.1)
check("context_config multi-window (3) with default 200K",
len(cn38.get("context_config") or {}) == 3
and (cn38["context_config"].get("200K") or {}).get("is_default") is True)
tc = (cn38.get("thinking_config") or {}).get("enabled") or {}
effs = tc.get("efforts") or {}
check("thinking efforts low/medium/xhigh with default medium",
set(effs) == {"low", "medium", "xhigh"}
and (effs.get("medium") or {}).get("is_default") is True, sorted(effs))
# 展示 id 与解析
check("display id format key (Name)",
C.display_id(cn38) == "qmodel_38max (Qwen3.8-Max)", C.display_id(cn38))
check("resolve display id -> key",
C.resolve_upstream_key("qmodel_38max (Qwen3.8-Max)", realm="cn") == "qmodel_38max")
check("resolve official display name (case-insensitive)",
C.resolve_upstream_key("qwen3.8-max", realm="cn") == "qmodel_38max"
and C.resolve_upstream_key("GLM-5.2", realm="cn") == "gm51model")
check("resolve bare display name DeepSeek-V4-Pro",
C.resolve_upstream_key("DeepSeek-V4-Pro", realm="cn") == "dmodel")
check("format_model_id helper",
C.format_model_id("gm51model", realm="cn") == "gm51model (GLM-5.2)",
C.format_model_id("gm51model", realm="cn"))
# model_entry 输出
me = P.model_entry("qmodel_38max", cn38)
check("model_entry id = OFFICIAL model name (the value clients fill in)",
me["id"] == "Qwen3.8-Max", me["id"])
check("model_entry upstream_key kept", me["upstream_key"] == "qmodel_38max")
check("model_entry aliases cover key + bracket form + friendly alias",
"qmodel_38max" in me["aliases"] and "qmodel_38max (Qwen3.8-Max)" in me["aliases"]
and "qwen3.8-max" in me["aliases"], me.get("aliases"))
# issue #68:文案会随官方文档更新(当前 qmodel_38max 是「Qwen 最新基座模型…」)。
# 断言不再硬编码某个词,而是比对**文案源**本身,避免每次改文案都要动测试。
check("model_entry description = official desktop copy",
bool(me.get("description"))
and me.get("description") == C.official_description("qmodel_38max"),
(me.get("description") or "")[:60])
check("model_entry enabled flag", me["enabled"] is True)
check("model_entry peak/valley factors",
me["price_factor_peak"] == 0.5 and me["price_factor_valley"] == 0.2)
check("model_entry off_peak window+badge",
me["off_peak_window"] == "22:00-08:00" and bool(me["off_peak"]["badge"]))
check("model_entry context labels default 200K",
me["context_window_labels"] == ["200K", "400K", "1M"] or set(me["context_window_labels"]) == {"200K", "400K", "1M"},
me.get("context_window_labels"))
check("model_entry context_window_default", me.get("context_window_default") == "200K")
check("model_entry reasoning efforts + default",
me.get("reasoning_efforts") == ["low", "medium", "xhigh"]
and me.get("reasoning_default_effort") == "medium",
(me.get("reasoning_efforts"), me.get("reasoning_default_effort")))
check("model_entry can_disable", me.get("reasoning_can_disable") is True)
check("model_entry is_free/is_new", me.get("is_free") is True and me.get("is_new") is True)
check("model_entry does NOT fabricate max_output_tokens (official data has none)",
"max_output_tokens" not in me and "max_completion_tokens" not in me,
sorted(k for k in me if "output" in k))
me_off = P.model_entry("smodel", entry_i)
check("model_entry disabled shows enabled=false (badge, not filtered)",
me_off["enabled"] is False)
check("model_entry disabled_reason = OFFICIAL copy (not '未开放')",
me_off.get("disabled_reason") == "需要升级或购买千问官方套餐开放",
me_off.get("disabled_reason"))
check("model_entry disabled_message_key passthrough (codeSafeModelReason)",
me_off.get("disabled_message_key") == "codeSafeModelReason",
me_off.get("disabled_message_key"))
check("official text loader: 17 zh descriptions",
len(C.load_official_text()["descriptions"]) == 17)
check("official local name ultimate -> zh",
C.official_local_name("ultimate") != "" and C.official_local_name("ultimate") != "Ultimate",
C.official_local_name("ultimate"))
me_u = P.model_entry("ultimate", next(m for m in C.STATIC_INTL_MODELS if m["key"] == "ultimate"))
check("model_entry name_local emitted for intl mode preset",
bool(me_u.get("name_local")), me_u.get("name_local"))
check("resolve official local label (Kimi-K2.7-Code)",
C.resolve_upstream_key("Kimi-K2.7-Code", realm="intl") == "kmodel")
check("cross-region guard accepts display id form",
P.exclusive_realm("gm51model (GLM-5.2)") == "cn")
print()
print("[5.8] off-peak (低谷) window detection — cross-midnight 22:00-08:00 UTC+8")
import datetime as _dt
def _ts(h, m):
# 构造 UTC+8 指定时刻对应的 epoch(固定 +8 与官方时区一致)。
# naive.timestamp() 把墙钟按**系统本地时区**解释;先加回本地 offset 得到
# 「h:m 视作 UTC」的 epoch,再减 8h 得到「h:m 在 UTC+8」的 epoch。
# 旧版把 offset 的符号写反:本地恰好是 UTC+8 时两个错误抵消所以一直绿,
# CI 的 UTC runner 上(offset=0)构造出的时刻偏移 -16h,5 条窗口断言全红
# ——「测试只在本地跑」的盲区第一次被 CI 实际抓住。
target_local_naive = _dt.datetime.now().replace(hour=h, minute=m, second=0, microsecond=0)
local_offset = _dt.datetime.now().astimezone().utcoffset().total_seconds()
return target_local_naive.timestamp() + (local_offset - 8 * 3600)
for hh, mm, expect, label in [
(1, 0, True, "01:00 inside window"),
(12, 0, False, "12:00 outside"),
(21, 59, False, "21:59 before window"),
(22, 0, True, "22:00 window start (inclusive)"),
(23, 30, True, "23:30 inside"),
(7, 59, True, "07:59 last minute inside"),
(8, 0, False, "08:00 window end (exclusive)")]:
got = P.off_peak_active_now("22:00", "08:00", tz="Asia/Shanghai",
now=_ts(hh, mm))
check(f"window {label}", got is expect, f"got={got} expect={expect}")
check("invalid window -> None",
P.off_peak_active_now(None, "08:00") is None)
check("same start/end -> always active",
P.off_peak_active_now("00:00", "00:00", now=_ts(13, 0)) is True)
# promotion fields surface off_peak_active_now via model_entry
me_promo = P.model_entry("qmodel_38max", cn38)
check("model_entry exposes off_peak_active_now (bool)",
isinstance(me_promo.get("off_peak_active_now"), bool),
me_promo.get("off_peak_active_now"))
# None must not clobber static snapshot values in merge
dyn_null = [("qmodel_38max", {"key": "qmodel_38max",
"context_config": None,
"thinking_config": None,
"price_factor": 0.2})]
merged_null = dict(P.merge_catalog(dyn_null, realm="cn"))["qmodel_38max"]
check("merge: dynamic None does NOT clobber static context_config",
isinstance(merged_null.get("context_config"), dict)
and "200K" in (merged_null.get("context_config") or {}),
type(merged_null.get("context_config")).__name__)
check("merge: dynamic None does NOT clobber static thinking_config",
isinstance(merged_null.get("thinking_config"), dict))
check("merge: dynamic real value still overrides",
merged_null.get("price_factor") == 0.2)
print()
print("[5.9] ALL off-peak (低谷) promotion models — must be complete, not just one")
PROMO_KEYS = {"qmodel_38max", "qmodel_latest", "qmodel"}
for realm_name in ("cn", "intl"):
# 促销集合按"是否带官方 promotion 元数据"判定;promo.active 是快照抓取
# 时刻是否处于低谷时段(22:00-08:00),不该作为集合成员条件。
promo_models = {m["key"] for m in C.models_for_realm(realm_name)
if m.get("promotion")}
check(f"[{realm_name}] official promo set == the 3 off-peak models",
promo_models == PROMO_KEYS, sorted(promo_models))
# 每个促销模型都要产出完整 off_peak 输出(不止一个)
for k in sorted(PROMO_KEYS):
src = next(m for m in C.models_for_realm(realm_name) if m["key"] == k)
e = P.model_entry(k, src)
check(f"[{realm_name}] {k} entry carries off_peak window+badge",
bool(e.get("off_peak")) and e.get("off_peak_window") == "22:00-08:00"
and bool(e.get("off_peak", {}).get("badge")),
e.get("off_peak_window"))
check(f"[{realm_name}] {k} entry has off_peak_active_now bool",
isinstance(e.get("off_peak_active_now"), bool))
check(f"[{realm_name}] {k} entry exposes peak/valley pair",
e.get("price_factor_peak") is not None
and e.get("price_factor_valley") is not None,
(e.get("price_factor_peak"), e.get("price_factor_valley")))
# Qwen3.8-Max: is_free=true 绝不能吞掉它的 promotion(看板曾把它渲染成
# 0.00x 免费并吃掉低谷高亮)
me_freeflag = P.model_entry("qmodel_38max",
next(m for m in C.STATIC_CN_MODELS
if m["key"] == "qmodel_38max"))
check("qmodel_38max is_free=true still carries promotion (peak 0.5 / valley 0.2)",
me_freeflag.get("is_free") is True and me_freeflag.get("price_factor_peak") == 0.5
and me_freeflag.get("price_factor_valley") == 0.2,
(me_freeflag.get("is_free"), me_freeflag.get("price_factor_peak"),
me_freeflag.get("price_factor_valley")))
# 看板分支顺序回归:promo 分支必须在 0 价分支之前
_dash = open(os.path.join(os.path.dirname(os.path.abspath(__file__)),
"dashboard.html"), encoding="utf-8").read()
_i_promo = _dash.find("if(promo && valley != null)")
_i_free = _dash.find("valley === 0")
check("dashboard: promotion branch BEFORE free branch (highlight no longer swallowed)",
0 <= _i_promo < _i_free, {"promo": _i_promo, "free": _i_free})
check("dashboard: is_free no longer triggers the 0.00x branch",
"m.is_free || valley === 0" not in _dash)
print()
print("[11] transient upstream errors (418/provider_error) — retry not punish")
import time as _time_for_11
time = _time_for_11 # 本块直接使用 time.time()/sleep
# 分类判定
check("418 + provider_error is transient",
P._is_transient_upstream(418, '{"code":"provider_error","message":"Error in upstream response"}'))
check("503 is transient", P._is_transient_upstream(503, ""))
check("client param error (invalid_parameter) NEVER transient",
not P._is_transient_upstream(400,
'{"code":"provider_error","details":"data: {\\"error\\":{\\"code\\":'
'\\"invalid_parameter_error\\",\\"message\\":\\"Range of max_tokens should be [1, 131072]\\"}"'))
check("plain 400 without provider_error not transient",
not P._is_transient_upstream(400, '{"code":"bad_request"}'))
check("401 never transient", not P._is_transient_upstream(401, "provider_error"))
# 行为级:第一次 418(瞬时) → 重试后成功,账号不背锅
import urllib.error as _ue3, io as _io3
_orig_urlopen = P.urllib.request.urlopen
_calls = {"n": 0}
class _FakeResp(object):
def __enter__(self): return self
def __exit__(self, *a): return False
def read(self, *a): return b"{}"
def _urlopen_fail_once(req, timeout=None):
_calls["n"] += 1
if _calls["n"] == 1:
raise _ue3.HTTPError(req.full_url, 418, "teapot", {},
_io3.BytesIO(b'{"code":"provider_error","message":"Error in upstream response"}'))
return _FakeResp()
try:
P.urllib.request.urlopen = _urlopen_fail_once
# 构造单账号池
import tempfile as _tf
_td = _tf.mkdtemp(prefix="qdpool_")
_pool = A.AccountPool(_td)
_pool.add(A.Account({"uid": "retry-test-uid", "realm": "cn",
"accessToken": "dt-test", "refreshToken": "drt-test",
"expiresAt": 9999999999}))
_orig_pool = P.POOL
P.POOL = _pool
_acc = _pool.accounts[0]
_acc.cooldown_until = 0
t0 = time.time()
resp, used, _ = P.open_upstream(
{"model": "qfmodel", "messages": [{"role": "user", "content": "hi"}],
"stream": False}, target_realm="cn")
took = time.time() - t0
check("418-then-success: open_upstream returns after in-place retry",
resp is not None and _calls["n"] == 2,
{"calls": _calls["n"]})
check("418-then-success: account NOT cooled down",
_acc.cooldown_until <= time.time(),
round(_acc.cooldown_until - time.time(), 1))
check("418-then-success: backoff took ~1s (not instant, not 60s)",
0.8 <= took <= 4.0, round(took, 2))
finally:
P.urllib.request.urlopen = _orig_urlopen
# 行为级:持续 418 → 重试耗尽后短冷却(单账号 3s 而非 60s)并抛出原错误
def _urlopen_always_418(req, timeout=None):
_calls["n"] += 1
raise _ue3.HTTPError(req.full_url, 418, "teapot", {},
_io3.BytesIO(b'{"code":"provider_error","message":"Error in upstream response"}'))
_calls["n"] = 0
try:
P.urllib.request.urlopen = _urlopen_always_418
P.POOL = _pool # 上一块 finally 还原了 None,这里重新挂上临时池
_acc.cooldown_until = 0
_acc.last_error = ""
raised = None
t0 = time.time()
try:
P.open_upstream({"model": "qfmodel",
"messages": [{"role": "user", "content": "hi"}],
"stream": False}, target_realm="cn")
except _ue3.HTTPError as e:
raised = e
took = time.time() - t0
check("persistent 418: raised after exactly 3 tries",
raised is not None and raised.code == 418 and _calls["n"] == 3,
{"calls": _calls["n"], "code": getattr(raised, "code", None)})
check("persistent 418: short cooldown (<=5s, single-account pool)",
0 < (_acc.cooldown_until - time.time()) <= 5.5,
round(_acc.cooldown_until - time.time(), 2))
check("persistent 418: bounded backoff time (~3s)",
2.0 <= took <= 6.0, round(took, 2))
finally:
P.urllib.request.urlopen = _orig_urlopen
P.POOL = _orig_pool
import shutil as _sh
_sh.rmtree(_td, ignore_errors=True)
# 传输层瞬时故障分类(TLS EOF 等)
import urllib.error as _ue4
import ssl as _ssl_for_11
check("URLError wrapping SSL EOF is transient transport",
P._is_transient_transport(_ue4.URLError(_ssl_for_11.SSLError(
"[SSL: UNEXPECTED_EOF_WHILE_READING] EOF"))))
check("ConnectionResetError is transient transport",
P._is_transient_transport(ConnectionResetError("reset")))
check("plain ValueError NOT transient transport",
not P._is_transient_transport(ValueError("nope")))
check("predicate: URLError+provider401 not transient-http",
not P._is_transient_upstream(401, "x"))
# 友好错误映射
_m, _t = P.friendly_upstream_error(418,
'{"code":"provider_error","message":"Error in upstream response"}')
check("friendly: 418 provider_error -> Chinese retry guidance",
"上游瞬时故障" in _m and "请稍后重试" in _m, _m[:60])
check("friendly: err_type tagged transient",
_t == "upstream_transient_error", _t)
_m2, _t2 = P.friendly_upstream_error(400,
'{"code":"provider_error","details":"invalid_parameter_error Range"}')
check("friendly: client param error NOT reframed as transient",
_t2 == "upstream_error" and "上游瞬时故障" not in _m2, (_t2, _m2[:50]))
check("friendly: detail preserved in message",
"provider_error" in _m)
# qoder_detail passthrough(错误体只读一次的修复)——挂在持续418的断言上补充
check("raised HTTPError carries qoder_detail for handlers",
hasattr(raised, "qoder_detail") and "provider_error" in raised.qoder_detail,
getattr(raised, "qoder_detail", "")[:60])
print()
print("[12] in-stream envelope retry (200-then-418 form — the reported log shape)")
# 状态归一化
check("_to_int_status str/int/fallback", P._to_int_status("418") == 418
and P._to_int_status(503) == 503 and P._to_int_status("xx") == 502)
_env418 = P.UpstreamStatus(
418, '{"code":"provider_error","message":"Error in upstream response"}')
check("fresh envelope 418 transient -> retry",
P.should_retry_envelope(_env418, emitted_bytes=False, attempt=0))
check("already emitted bytes -> NO retry",
not P.should_retry_envelope(_env418, emitted_bytes=True, attempt=0))
check("budget exhausted -> NO retry",
not P.should_retry_envelope(_env418, False, P.TRANSIENT_MAX_RETRIES))
_env_param = P.UpstreamStatus(400, "invalid_parameter_error Range of max_tokens")
check("client param envelope -> NO retry",
not P.should_retry_envelope(_env_param, False, 0))
# aggregate_with_envelope_retry: 第一次信封418 → 重开上游 → 成功
_sleeps = []
_orig_sleep2 = P.time.sleep
_orig_open2 = P.open_upstream
_pools = {"calls": 0}
class _GoodResp(object):
def __iter__(self):
inner = json.dumps({"choices": [{"delta": {"content": "recovered"}}]})
yield ("data: " + json.dumps({"statusCodeValue": 200, "body": inner})
+ "\n\n").encode("utf-8")
yield b'data: {"statusCodeValue":200,"body":"[DONE]"}\n\n'
def close(self):
pass
class _ErrResp(object):
def __iter__(self):
yield ("data: " + json.dumps({
"statusCodeValue": 418,
"body": '{"code":"provider_error","message":"Error in upstream response"}'})
+ "\n\n").encode("utf-8")
def close(self):
pass
def _fake_open(payload, session_key=None, target_realm=None):
_pools["calls"] += 1
return _GoodResp(), "acct-1", "enc"
class _A(object):
uid = "acct-1"
try:
P.time.sleep = lambda s: _sleeps.append(s)
P.open_upstream = _fake_open
obj, acc = P.aggregate_with_envelope_retry(
_ErrResp(), {"model": "qfmodel"}, None, "cn", "qfmodel",
{"usage": None}, _A())
check("envelope 418 -> reopened upstream and recovered",
obj["choices"][0]["message"]["content"] == "recovered",
obj["choices"][0]["message"].get("content"))
check("reopen happened exactly once", _pools["calls"] == 1, _pools["calls"])
check("backoff 1s recorded", _sleeps == [1], _sleeps)
finally:
P.time.sleep = _orig_sleep2
P.open_upstream = _orig_open2
# 非瞬时信封(客户端参数错)不重开、原样上抛
_sleeps2 = []
_pools2 = {"calls": 0}
print()
print("[13] content-policy rejection (DataInspectionFailed — 02:27 log root cause)")
_DI_DETAIL = ('{"error":{"message":"\\u003c400\\u003e InternalError.Algo.'
'DataInspectionFailed: Input text data may contain inappropriate '
'content.","type":"UnknownError"}}')
check("DataInspection detail -> NOT transient (no wasted retries)",
not P._is_transient_upstream(418, _DI_DETAIL))
check("DataInspection envelope -> should_retry_envelope False",
not P.should_retry_envelope(
P.UpstreamStatus(418, _DI_DETAIL), emitted_bytes=False, attempt=0))
_m13, _t13 = P.friendly_upstream_error(418, _DI_DETAIL)
check("friendly: content-policy Chinese explanation",
"内容安全审核未通过" in _m13 and "重试无效" in _m13, _m13[:70])
check("friendly: err_type content_policy_rejected",
_t13 == "content_policy_rejected", _t13)
check("friendly: original detail preserved",
"DataInspectionFailed" in _m13)
print()
print("[14] error-cooldown vs upstream-frequency (no misleading 429)")
import time as _t14
# 构造临时池:账号仅处于错误冷却
_td14 = __import__("tempfile").mkdtemp(prefix="qd14_")
_pool14 = A.AccountPool(_td14)
_acc14 = A.Account({"uid": "u14", "realm": "cn", "accessToken": "dt-x",
"refreshToken": "drt-x", "expiresAt": 9999999999})
_pool14.add(_acc14)
_orig_pool14 = P.POOL
P.POOL = _pool14
try:
# (a) 仅账号错误冷却 -> 不算频控
_acc14.cooldown_until = _t14.time() + 5
_acc14.model_cooldowns.clear()
throttled, w = P.realm_model_throttled("cn", "qfmodel")
check("account error-cooldown is NOT a frequency-limit (no 429)",
throttled is False, (throttled, w))
check("retry_after_seconds ignores account cooldown",
P.retry_after_seconds("qfmodel", "cn") == 60,
P.retry_after_seconds("qfmodel", "cn"))
wait = P._short_error_cooldown_wait("cn", "qfmodel")
check("short error-cooldown wait surfaced (<=10s, >0)",
0 < wait <= 10, wait)
# (b) 上游频控 -> 正当429
_acc14.cooldown_until = 0
_acc14.model_cooldowns["qfmodel"] = _t14.time() + 60
throttled2, w2 = P.realm_model_throttled("cn", "qfmodel")
check("model_cooldowns (upstream 429) IS frequency-limit",
throttled2 is True and w2 >= 59, (throttled2, w2))
check("short wait suppressed while frequency-limited",
P._short_error_cooldown_wait("cn", "qfmodel") == 0.0)
check("retry_after reflects frequency wait",
59 <= P.retry_after_seconds("qfmodel", "cn") <= 61,
P.retry_after_seconds("qfmodel", "cn"))
# (c) 行为:错误短冷却 -> 等待后续上(真实 sleep ~0.3s)而不是429
_acc14.model_cooldowns.clear()
_acc14.cooldown_until = _t14.time() + 0.3
_orig_urlopen14 = P.urllib.request.urlopen
class _R14(object):
def __iter__(self):
inner = json.dumps({"choices": [{"delta": {"content": "after-wait"}}]})
yield ("data: " + json.dumps({"statusCodeValue": 200, "body": inner})
+ "\n\n").encode()
yield b'data: {"statusCodeValue":200,"body":"[DONE]"}\n\n'
def close(self):
pass
def __enter__(self):
return self
def __exit__(self, *a):
return False
def _ok_urlopen(req, timeout=None):
return _R14()
try:
P.urllib.request.urlopen = _ok_urlopen
_sleep_used = []
_t0 = _t14.time()
try:
resp, used, _ = P.open_upstream(
{"model": "qfmodel",
"messages": [{"role": "user", "content": "hi"}],
"stream": False}, target_realm="cn")
_served = True
_rate = None
except Exception as _e:
_served = False
_rate = _e
took = _t14.time() - _t0
check("short cooldown: request WAITS and serves (no 429/503)",
_served and _rate is None,
{"served": _served, "err": repr(_rate)})
check("wait lasted ~0.3-1s (bounded)", 0.25 <= took <= 2.0,
round(took, 2))
finally:
P.urllib.request.urlopen = _orig_urlopen14
# (d) 频控行为不变:真429 仍然立刻 RateLimited
_acc14.cooldown_until = 0
_acc14.model_cooldowns["qfmodel"] = _t14.time() + 60
try:
P.urllib.request.urlopen = _ok_urlopen
_raised14 = None
try:
P.open_upstream({"model": "qfmodel",
"messages": [{"role": "user", "content": "hi"}],
"stream": False}, target_realm="cn")
except Exception as e14:
_raised14 = e14
check("genuine frequency limit still raises RateLimited fast",
isinstance(_raised14, P.RateLimited)
and "frequency" in str(getattr(_raised14, "detail", "")),
repr(_raised14))
finally:
P.urllib.request.urlopen = _orig_urlopen14
finally:
P.POOL = _orig_pool14
_acc14.model_cooldowns.clear()
_acc14.cooldown_until = 0
import shutil as _sh14
_sh14.rmtree(_td14, ignore_errors=True)
# favicon 404 静默
check("log_message silences favicon regardless of status (code present)",
'req_path == "/favicon.ico"' in open(
os.path.join(os.path.dirname(os.path.abspath(__file__)),
"qoder_proxy.py"), encoding="utf-8").read())
print()
print("[15] HTTP/1.1 SSE framing — keep-alive friendly (no more reconnect loop)")
_src15 = open(os.path.join(os.path.dirname(os.path.abspath(__file__)),
"qoder_proxy.py"), encoding="utf-8").read()
check("handler defines chunked SSE helpers",
all(k in _src15 for k in ("def _sse_begin", "def _sse_write",
"def _sse_end")))
check("_sse_begin sends Transfer-Encoding: chunked",
'self.send_header("Transfer-Encoding", "chunked")' in _src15)
check("_sse_end writes terminating zero chunk",
'b"0\\r\\n\\r\\n"' in _src15)
check("no 'Connection: close' on streaming responses (root cause of reconnect loop)",
'self.send_header("Connection", "close")' not in _src15)
check("streaming writes go through _sse_write (chunk-encoded)",
"self.wfile.write(line)" not in _src15
and "self.wfile.write(clean_responses_frame(frame))" not in _src15)
check("chunked streams are terminated on every exit path",
_src15.count("self._sse_end()") >= 4, _src15.count("self._sse_end()"))
print()
print("[16] liveness probe endpoints (GET /ping was 404 -> clients reconnect loop)")
check("/ping served without auth/panel",
'if path in ("/ping", "/healthz", "/livez", "/readyz"):' in _src15)
check("/ping returns plain pong", 'body = b"pong\\n"' in _src15)
check("/ping sits before the /health handler (earliest match)",
_src15.find('if path in ("/ping"') < _src15.find('if path == "/health":'))
print()
print("[17] SSE heartbeat during long upstream silence (TTFT up to 71s observed)")
check("sse_with_heartbeat helper exists",
"def sse_with_heartbeat(" in _src15)
check("heartbeat emits SSE comment frames (': ping')",
'b": ping\\n\\n"' in _src15)
check("heartbeat enabled by default with env override",
'os.environ.get("QD_SSE_HEARTBEAT"' in _src15)
check("both streaming paths wrap their source with heartbeat",
_src15.count("sse_with_heartbeat(") >= 3,
_src15.count("sse_with_heartbeat("))
# 行为级:数据透传 / 空闲补心跳 / 上游异常原样抛出 / 可关闭
_sent2, _slow = [], []
def _slow_gen():
time.sleep(0.4) # 模拟长首字延迟(上游静默)
yield b"data: late\n\n"
time.sleep(0.4) # 模拟帧间静默
yield b"data: late2\n\n"
for item in P.sse_with_heartbeat(_slow_gen(), _sent2.append, interval=0.15,
idle_limit=10):
_slow.append(item)
check("heartbeat: data passes through unchanged",
_slow == [b"data: late\n\n", b"data: late2\n\n"], _slow)
check("heartbeat: comment frames sent during both silences",
len(_sent2) >= 2 and all(f == b": ping\n\n" for f in _sent2),
(len(_sent2), _sent2))
class _BoomErr(RuntimeError):
pass
def _boom_gen():
yield b"data: first\n\n"
raise _BoomErr("upstream died")
_sent3, _got3, _raised3 = [], [], None
try:
for item in P.sse_with_heartbeat(_boom_gen(), _sent3.append, interval=0.15,
idle_limit=5):
_got3.append(item)
except Exception as exc:
_raised3 = exc
check("heartbeat: upstream error propagates unchanged",
isinstance(_raised3, _BoomErr) and _got3 == [b"data: first\n\n"],
(type(_raised3).__name__, _got3))
def _one_gen():
yield b"data: only\n\n"
_sent4, _got4 = [], []
for item in P.sse_with_heartbeat(_one_gen(), _sent4.append, interval=0,
idle_limit=5):
_got4.append(item)
check("heartbeat: interval=0 disables (pure pass-through)",
_got4 == [b"data: only\n\n"] and _sent4 == [], (_got4, _sent4))
# 行为:内容审核信封 -> 一次都不重开(open_upstream 不被再次调用)并上抛
class _DiErrResp(object):
def __iter__(self):
yield ("data: " + json.dumps({
"statusCodeValue": 418, "body": _DI_DETAIL}) + "\n\n").encode()
def close(self):
pass
def __enter__(self):
return self
def __exit__(self, *a):
return False
_pools13 = {"calls": 0}
_sleeps13 = []
def _fake_open_never13(payload, session_key=None, target_realm=None):
_pools13["calls"] += 1
return _GoodResp(), "acct-1", "enc"
try:
P.time.sleep = lambda s: _sleeps13.append(s)
P.open_upstream = _fake_open_never13
_raised13 = None
try:
P.aggregate_with_envelope_retry(
_DiErrResp(), {"model": "Qwen3.8-Flash"}, None, "cn",
"Qwen3.8-Flash", {"usage": None}, _A())
except P.UpstreamStatus as e13:
_raised13 = e13
check("content-policy envelope: raised immediately, ZERO reopen, ZERO sleep",
_raised13 is not None and _pools13["calls"] == 0 and not _sleeps13,
{"raised": _raised13 is not None, "reopen": _pools13["calls"],
"sleeps": _sleeps13})
finally:
P.time.sleep = _orig_sleep2
P.open_upstream = _orig_open2
# 非瞬时信封(客户端参数错)不重开、原样上抛
_sleeps2 = []
_pools2 = {"calls": 0}
class _ParamErrResp(object):
def __iter__(self):
yield ("data: " + json.dumps({
"statusCodeValue": 400,
"body": "invalid_parameter_error Range of max_tokens [1, 131072]"})
+ "\n\n").encode("utf-8")
def close(self):
pass
def _fake_open_never(payload, session_key=None, target_realm=None):
_pools2["calls"] += 1
return _GoodResp(), "acct-1", "enc"
try:
P.time.sleep = lambda s: _sleeps2.append(s)
P.open_upstream = _fake_open_never
_raised_env = None
try:
P.aggregate_with_envelope_retry(
_ParamErrResp(), {"model": "qfmodel"}, None, "cn", "qfmodel",
{"usage": None}, _A())
except P.UpstreamStatus as e0:
_raised_env = e0
check("param envelope raises immediately (no reopen)",
_raised_env is not None and _pools2["calls"] == 0,
{"raised": _raised_env is not None, "reopen": _pools2["calls"]})
finally:
P.time.sleep = _orig_sleep2
P.open_upstream = _orig_open2
ctx = {m["key"]: m.get("max_input_tokens") for m in C.STATIC_CN_MODELS}
check("cn dmodel ctx = official 96000 (NOT a guess)", ctx.get("dmodel") == 96000,
ctx.get("dmodel"))
ctx_i = {m["key"]: m.get("max_input_tokens") for m in C.STATIC_INTL_MODELS}
check("intl dmodel ctx = official 1000000", ctx_i.get("dmodel") == 1000000,
ctx_i.get("dmodel"))
pf = {m["key"]: m.get("price_factor") for m in C.STATIC_CN_MODELS}
check("price_factor carried from official catalog",
pf.get("qfmodel") == 0.0 and pf.get("dmodel") == 0.5, pf.get("dmodel"))
check("exclusive sets derived from catalogs",
"gm51model" in C.CN_EXCLUSIVE and "smodel" in C.INTL_EXCLUSIVE)
check("exclusive realm detection: gm51model -> cn",
P.exclusive_realm("gm51model") == "cn")
check("exclusive realm detection: smodel -> intl",
P.exclusive_realm("smodel") == "intl")
check("alias resolves before exclusive check: glm-5.2 -> cn",
P.exclusive_realm("glm-5.2") == "cn")
check("shared key has no exclusive owner", P.exclusive_realm("qmodel") == "")
check("detect_model_realm routes cn-exclusive to cn even under intl default",
P.detect_model_realm("q37fmodel") == "cn")
check("detect_model_realm routes intl-exclusive to intl",
P.detect_model_realm("performance") == "intl")
check("shared model follows current default realm",
P.detect_model_realm("qmodel") == P.CURRENT_REALM)
print()
print("[5.6] check-in capability is probed at runtime (not hard-coded per realm)")
import qoder_accounts as _A
import urllib.error as _ue2, io as _io2
check("cn has_checkin hint True", _A.get_realm_config("cn")["has_checkin"] is True)
check("intl has_checkin hint False (still only a hint)",
_A.get_realm_config("intl")["has_checkin"] is False)
acc_intl = _A.Account({"uid": "i1", "realm": "intl", "accessToken": "dt-x"})
acc_cn = _A.Account({"uid": "c1", "realm": "cn", "accessToken": "dt-y"})
# 未探测前不按区域拒绝:国际版同样会真的去尝试一次(实测国际版接口 404,
# 由探测结果决定后续跳过,而不是"看区域直接不做")
check("intl account is attempted before probing (realm is not a gate)",
acc_intl.can_checkin() is True and acc_intl.checkin_capability()[0] is None)
check("cn account can checkin", acc_cn.can_checkin() is True)
# 国际版形态:/daily-check-in/* 404 -> 能力记录为不可用 + 明确原因(不是静默)
def fake_status_404(url, **kw):
raise _ue2.HTTPError(url, 404, "nf", {}, _io2.BytesIO(b'{"errorCode":"NotFound"}'))
_orig_hj = _A.http_json
_A.http_json = fake_status_404
res_intl = acc_intl.checkin()
cap_intl, reason_intl = acc_intl.checkin_capability()
_A.http_json = _orig_hj
check("404 status -> (ok, unavailable) with explicit reason",
res_intl.get("ok") is True and res_intl.get("unavailable") is True
and res_intl.get("reason") == _A.CHECKIN_REASON_NOT_FOUND, res_intl)
check("404 message names the missing endpoint + hand-off to official client",
"daily-check-in" in str(res_intl.get("msg"))
and "客户端" in str(res_intl.get("msg")), res_intl.get("msg"))
check("capability cached as unavailable -> can_checkin False (no repeated 404s)",
cap_intl is False and acc_intl.can_checkin() is False and "404" in reason_intl)
# TTL 到期后回到"未探测",下一次调用会真的重探(活动上线即自动恢复)
acc_intl._checkin_cap_at -= (_A.CHECKIN_PROBE_TTL + 1)
check("TTL 过期 -> capability 回到 unknown,下一次触发重探",
acc_intl.checkin_capability()[0] is None and acc_intl.can_checkin() is True)
# 官方活动停用 (DISABLED) -> 不 claim、按跳过成功处理
def fake_status_disabled(url, **kw):
return {"campaignKey": "cn_daily_check_in_legacy", "status": "DISABLED",
"rewardCredits": 100, "currentStreakDays": 0, "totalClaimDays": 0,
"totalRewardCredits": 0}
_A.http_json = fake_status_disabled
acc_dis = _A.Account({"uid": "d1", "realm": "cn", "accessToken": "dt-x"})
res_dis = acc_dis.checkin()
cap_ok, _why = acc_dis.checkin_capability()
_A.http_json = _orig_hj
check("DISABLED campaign -> ok+disabled (no claim POST)",
res_dis.get("ok") is True and res_dis.get("disabled") is True, res_dis)
check("realm-capable status response marks capability available",
cap_ok is True, cap_ok)
# pro eligibility 404 -> 查询成功但不可领取
def fake_pro_404(url, **kw):
raise _ue2.HTTPError(url, 404, "nf", {}, _io2.BytesIO(b""))
_A.http_json = fake_pro_404
ok_p, elig_p = acc_dis.pro_eligibility()
_A.http_json = _orig_hj
check("pro eligibility 404 -> (queried, not eligible)",
ok_p is True and elig_p is False, (ok_p, elig_p))
print()
print("[5.7] campaign platform (/sash/api/v1/me/campaigns) — the new daily-claim home")
_acc_camp = _A.Account({"uid": "cp1", "realm": "intl", "accessToken": "dt-x"})
check("campaigns path constant present (same path on cn + intl)",
_A.PATH_CAMPAIGNS == "/sash/api/v1/me/campaigns")
def fake_campaigns(url, **kw):
check("campaigns hit the openapi base of the account realm",