package gonexapp_test import ( "context" "encoding/base64" "encoding/json" "fmt" "net/http" "net/http/httptest" "strings" "testing" "time" "github.com/chromedp/cdproto/network" "github.com/chromedp/chromedp" "gitea.neitzel.de/konrad/go-nc-exapp" ) const browserLanguage = "de-DE,de;q=0.9" func TestUILanguageChrome(t *testing.T) { german := chromeSeen{ lang: "de", open: "Navigation öffnen", close: "Navigation schließen", fold: "Domains einklappen", cancel: "Abbrechen", ok: "OK", } english := chromeSeen{ lang: "en", open: "Open navigation", close: "Close navigation", fold: "Fold Domains", cancel: "Cancel", ok: "OK", } tests := []struct { name string user bool status int ncLang string want chromeSeen }{ {name: "german", user: true, status: http.StatusOK, ncLang: "de", want: german}, {name: "english", user: true, status: http.StatusOK, ncLang: "en", want: english}, {name: "formal german", user: true, status: http.StatusOK, ncLang: "de_DE", want: german}, {name: "british english", user: true, status: http.StatusOK, ncLang: "en_GB", want: english}, {name: "french", user: true, status: http.StatusOK, ncLang: "fr", want: english}, {name: "failed read", user: true, status: http.StatusInternalServerError, ncLang: "de", want: english}, {name: "missing user", user: false, status: http.StatusOK, ncLang: "de", want: english}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { nc := fakeLanguage(tt.status, tt.ncLang) t.Cleanup(nc.Close) page := languagePage(nc.URL) t.Cleanup(page.Close) headers := network.Headers{"Accept-Language": browserLanguage} if tt.user { headers["AUTHORIZATION-APP-API"] = base64.StdEncoding.EncodeToString([]byte("alice:secret")) } got := readChrome(t, page.URL+"/?item=domains", headers) if !strings.Contains(got.page, "domains-page") { t.Fatalf("page did not render: %+v", got) } if got.lang != tt.want.lang || got.open != tt.want.open || got.close != tt.want.close || got.fold != tt.want.fold || got.cancel != tt.want.cancel || got.ok != tt.want.ok || got.agree != "Remove" { t.Fatalf("got %+v, want %+v with agree %q", got, tt.want, "Remove") } }) } } type chromeSeen struct { lang string open string close string fold string cancel string ok string agree string page string } func fakeLanguage(status int, language string) *httptest.Server { return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet || !strings.Contains(r.URL.Path, "/cloud/users/") { http.NotFound(w, r) return } if status != http.StatusOK { http.Error(w, "language unavailable", status) return } // A read that drops the browser language must not look like a hit. lang := language if r.Header.Get("Accept-Language") != browserLanguage { lang = "en" } // locale differs from language so a read of the wrong field fails the page. locale := "fr" if language == "fr" || language == "en_GB" { locale = "de" } w.Header().Set("Content-Type", "application/json") fmt.Fprintf(w, `{"ocs":{"meta":{"status":"ok","statuscode":200,"message":"OK"},"data":{"language":%q,"locale":%q}}}`, lang, locale) })) } func languagePage(ncBase string) *httptest.Server { nav := sampleNavigation() nav.Cred = gonexapp.Credentials{ BaseURL: ncBase, AppID: "exapp", AppVersion: "1.0.0", AAVersion: "1.0.0", AppSecret: "secret", } nav.Page = func(_ *http.Request, id string) (string, bool) { switch id { case "domains": return `

domains-page

`, true case "keys": return "

keys-page

", true case "example.com": return "

zone-page

", true default: return "", false } } return httptest.NewServer(nav.Handler()) } func readChrome(t *testing.T, pageURL string, headers network.Headers) chromeSeen { t.Helper() ctx, cancel := chromedp.NewContext(allocCtx) t.Cleanup(cancel) ctx, cancel = context.WithTimeout(ctx, 25*time.Second) t.Cleanup(cancel) var raw, closeLabel, agree string err := chromedp.Run(ctx, network.Enable(), network.SetExtraHTTPHeaders(headers), chromedp.Navigate(pageURL), chromedp.Evaluate(chromeProbeJS, &raw), chromedp.Evaluate(`document.getElementById("nav-toggle").click()`, nil), chromedp.Evaluate(`document.getElementById("nav-toggle").getAttribute("aria-label")`, &closeLabel), chromedp.Evaluate(`document.getElementById("ask").click()`, nil), chromedp.Evaluate(`document.getElementById("exapp-dialog-agree").textContent`, &agree), ) if err != nil { t.Fatal(err) } var probe struct { Lang string `json:"lang"` Open string `json:"open"` Fold string `json:"fold"` Cancel string `json:"cancel"` OK string `json:"ok"` Page string `json:"page"` } if err := json.Unmarshal([]byte(raw), &probe); err != nil { t.Fatal(err) } return chromeSeen{ lang: probe.Lang, open: probe.Open, close: closeLabel, fold: probe.Fold, cancel: probe.Cancel, ok: probe.OK, agree: agree, page: probe.Page, } } const chromeProbeJS = `(() => { const fold = document.querySelector("button.fold"); return JSON.stringify({ lang: document.documentElement.lang, open: document.getElementById("nav-toggle").getAttribute("aria-label"), fold: fold ? fold.getAttribute("aria-label") : "", cancel: document.getElementById("exapp-dialog-cancel").textContent, ok: document.getElementById("exapp-dialog-agree").textContent, page: document.querySelector("main").textContent }); })()`