package versionser import ( "encoding/json" "net/http" "net/http/httptest" "sync" "sync/atomic" "testing" "time" "91porn-server/app/appg" "91porn-server/common/localcache" ) func TestAdvVersionAnnounThreeServerCoalescesConcurrentMisses(t *testing.T) { var calls atomic.Int32 var badRequest atomic.Bool server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { calls.Add(1) if r.Method != http.MethodPost || r.URL.Path != "/api/stat/all/app" { badRequest.Store(true) w.WriteHeader(http.StatusBadRequest) _, _ = w.Write([]byte(`{}`)) return } time.Sleep(50 * time.Millisecond) _ = json.NewEncoder(w).Encode(AppAllResp{ VersionInfo: VersionInfo{ServerVersion: "9.9.9"}, AnnouList: []AnnouInfo{{ID: "announcement-1"}}, IosUrl: "ios-url", AndroidUrl: "android-url", ShopIosLink: "shop-ios-url", }) })) defer server.Close() setVersionTestConfig(t, server.URL) version := "singleflight-test" deleteVersionTestCache(t, "VerAnnInfo:"+version) const concurrency = 20 start := make(chan struct{}) errs := make(chan error, concurrency) var wg sync.WaitGroup for i := 0; i < concurrency; i++ { wg.Add(1) go func() { defer wg.Done() <-start ver, _, annou, iosURL, androidURL, shopURL, err := AdvVersionAnnounThreeServer(version, "android") if err == nil && (ver.ServerVersion != "9.9.9" || len(annou) != 1 || iosURL != "ios-url" || androidURL != "android-url" || shopURL != "shop-ios-url") { err = &unexpectedVersionResponseError{} } errs <- err }() } close(start) wg.Wait() close(errs) for err := range errs { if err != nil { t.Fatalf("unexpected result: %v", err) } } if got := calls.Load(); got != 1 { t.Fatalf("downstream calls = %d, want 1", got) } if badRequest.Load() { t.Fatal("downstream request used an unexpected method or path") } } func TestAdvVersionAnnounThreeServerCachesFailureFallback(t *testing.T) { var calls atomic.Int32 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { calls.Add(1) w.WriteHeader(http.StatusMethodNotAllowed) _, _ = w.Write([]byte(`{}`)) })) defer server.Close() setVersionTestConfig(t, server.URL) version := "failure-cache-test" deleteVersionTestCache(t, "VerAnnInfo:"+version) if _, _, _, _, _, _, err := AdvVersionAnnounThreeServer(version, "android"); err == nil { t.Fatal("first request error = nil, want downstream status error") } if _, _, _, _, _, _, err := AdvVersionAnnounThreeServer(version, "android"); err != nil { t.Fatalf("cached fallback error = %v, want nil", err) } if got := calls.Load(); got != 1 { t.Fatalf("downstream calls = %d, want 1", got) } } type unexpectedVersionResponseError struct{} func (*unexpectedVersionResponseError) Error() string { return "unexpected version response" } func setVersionTestConfig(t *testing.T, productURL string) { t.Helper() previous := appg.Conf conf := &appg.GlobalConfig{} conf.URL.ProductUrl = productURL appg.Conf = conf t.Cleanup(func() { appg.Conf = previous }) } func deleteVersionTestCache(t *testing.T, key string) { t.Helper() localcache.C.Delete(key) localcache.C.Delete(key + ":lastSuccess") t.Cleanup(func() { localcache.C.Delete(key) localcache.C.Delete(key + ":lastSuccess") }) }