Stage 16: deploy infra & test contour #17
@@ -175,7 +175,9 @@ jobs:
|
|||||||
TELEGRAM_MINIAPP_URL: ${{ vars.TEST_TELEGRAM_MINIAPP_URL }}
|
TELEGRAM_MINIAPP_URL: ${{ vars.TEST_TELEGRAM_MINIAPP_URL }}
|
||||||
TELEGRAM_GAME_CHANNEL_ID_EN: ${{ vars.TEST_TELEGRAM_GAME_CHANNEL_ID_EN }}
|
TELEGRAM_GAME_CHANNEL_ID_EN: ${{ vars.TEST_TELEGRAM_GAME_CHANNEL_ID_EN }}
|
||||||
TELEGRAM_GAME_CHANNEL_ID_RU: ${{ vars.TEST_TELEGRAM_GAME_CHANNEL_ID_RU }}
|
TELEGRAM_GAME_CHANNEL_ID_RU: ${{ vars.TEST_TELEGRAM_GAME_CHANNEL_ID_RU }}
|
||||||
TELEGRAM_TEST_ENV: ${{ vars.TEST_TELEGRAM_TEST_ENV }}
|
# The test contour always uses Telegram's test environment — pinned here,
|
||||||
|
# not an operator variable. Stage 17's prod workflow leaves it false.
|
||||||
|
TELEGRAM_TEST_ENV: "true"
|
||||||
VITE_TELEGRAM_BOT_ID: ${{ vars.TEST_VITE_TELEGRAM_BOT_ID }}
|
VITE_TELEGRAM_BOT_ID: ${{ vars.TEST_VITE_TELEGRAM_BOT_ID }}
|
||||||
VITE_TELEGRAM_LINK: ${{ vars.TEST_VITE_TELEGRAM_LINK }}
|
VITE_TELEGRAM_LINK: ${{ vars.TEST_VITE_TELEGRAM_LINK }}
|
||||||
VITE_GATEWAY_URL: ${{ vars.TEST_VITE_GATEWAY_URL }}
|
VITE_GATEWAY_URL: ${{ vars.TEST_VITE_GATEWAY_URL }}
|
||||||
|
|||||||
@@ -1089,6 +1089,12 @@ provided cert) at the contour caddy; prod VPN; rollback.
|
|||||||
on the runner host. CI bootstrap nuance: the first PR introducing `ci.yaml` may first deploy on the
|
on the runner host. CI bootstrap nuance: the first PR introducing `ci.yaml` may first deploy on the
|
||||||
post-merge push to `development` (depending on whether Gitea runs head/base workflows for a PR), after
|
post-merge push to `development` (depending on whether Gitea runs head/base workflows for a PR), after
|
||||||
which PR-time deploys work.
|
which PR-time deploys work.
|
||||||
|
- **Telegram test environment** (post-deploy fix): the connector now selects Telegram's test env with the
|
||||||
|
library's native `tgbot.UseTestEnvironment()` (was a `token += "/test"` hack — functionally identical,
|
||||||
|
verified, but the option is idiomatic and now has a `bot` test asserting the `/bot<token>/test/getMe`
|
||||||
|
path). The test contour **pins `TELEGRAM_TEST_ENV=true` in `ci.yaml`** (the contour is the test
|
||||||
|
environment) rather than via a `TEST_`-prefixed variable — removing a confusing double-`TEST` operator
|
||||||
|
knob and the secret-vs-variable footgun; prod (Stage 17) leaves it `false`.
|
||||||
|
|
||||||
## Deferred TODOs (cross-stage)
|
## Deferred TODOs (cross-stage)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -71,7 +71,7 @@ connector **fails at boot** if both are empty.
|
|||||||
| `GRAFANA_ADMIN_PASSWORD` | secret | `admin` | Grafana admin password. Low impact (the login form is disabled, access is anonymous-admin behind caddy) but set it anyway. |
|
| `GRAFANA_ADMIN_PASSWORD` | secret | `admin` | Grafana admin password. Low impact (the login form is disabled, access is anonymous-admin behind caddy) but set it anyway. |
|
||||||
| `TELEGRAM_GAME_CHANNEL_ID_EN` | variable | _(empty)_ | English game-channel id; empty/`0` disables channel posts. |
|
| `TELEGRAM_GAME_CHANNEL_ID_EN` | variable | _(empty)_ | English game-channel id; empty/`0` disables channel posts. |
|
||||||
| `TELEGRAM_GAME_CHANNEL_ID_RU` | variable | _(empty)_ | Russian game-channel id; empty/`0` disables channel posts. |
|
| `TELEGRAM_GAME_CHANNEL_ID_RU` | variable | _(empty)_ | Russian game-channel id; empty/`0` disables channel posts. |
|
||||||
| `TELEGRAM_TEST_ENV` | variable | `false` | `true` routes the bot through Telegram's test environment. |
|
| `TELEGRAM_TEST_ENV` | _pinned_ | `false` | `true` routes the bot through Telegram's test environment (`.../bot<token>/test/METHOD`). **The CI test contour pins this to `true` in `ci.yaml`** (the contour is the test environment) — it is not a Gitea variable. Set it in `.env` for a local run; prod (Stage 17) leaves it `false`. |
|
||||||
| `TELEGRAM_API_BASE_URL` | variable | _(empty)_ | Override the Bot API host (a mock/self-hosted server); empty = `https://api.telegram.org`. |
|
| `TELEGRAM_API_BASE_URL` | variable | _(empty)_ | Override the Bot API host (a mock/self-hosted server); empty = `https://api.telegram.org`. |
|
||||||
| `GATEWAY_DEFAULT_SUPPORTED_LANGUAGES` | variable | `en,ru` | Variant-gating set for non-Telegram logins (web/email/guest). |
|
| `GATEWAY_DEFAULT_SUPPORTED_LANGUAGES` | variable | `en,ru` | Variant-gating set for non-Telegram logins (web/email/guest). |
|
||||||
| `VITE_TELEGRAM_BOT_ID` | variable | _(empty)_ | UI build-arg: numeric bot id for the web Login Widget. |
|
| `VITE_TELEGRAM_BOT_ID` | variable | _(empty)_ | UI build-arg: numeric bot id for the web Login Widget. |
|
||||||
|
|||||||
@@ -43,21 +43,18 @@ func New(cfg Config, log *zap.Logger) (*Bot, error) {
|
|||||||
}
|
}
|
||||||
t := &Bot{miniAppURL: cfg.MiniAppURL, log: log}
|
t := &Bot{miniAppURL: cfg.MiniAppURL, log: log}
|
||||||
|
|
||||||
token := cfg.Token
|
|
||||||
if cfg.TestEnv {
|
|
||||||
// The Bot API test environment lives under /bot<token>/test/METHOD; the
|
|
||||||
// client builds <host>/bot<token>/<method>, so suffixing the token with
|
|
||||||
// "/test" injects the test segment without a custom host.
|
|
||||||
token += "/test"
|
|
||||||
}
|
|
||||||
opts := []tgbot.Option{
|
opts := []tgbot.Option{
|
||||||
tgbot.WithDefaultHandler(t.handleStart),
|
tgbot.WithDefaultHandler(t.handleStart),
|
||||||
tgbot.WithMessageTextHandler("/start", tgbot.MatchTypePrefix, t.handleStart),
|
tgbot.WithMessageTextHandler("/start", tgbot.MatchTypePrefix, t.handleStart),
|
||||||
}
|
}
|
||||||
|
if cfg.TestEnv {
|
||||||
|
// Route to the Bot API test environment (.../bot<token>/test/METHOD).
|
||||||
|
opts = append(opts, tgbot.UseTestEnvironment())
|
||||||
|
}
|
||||||
if cfg.APIBaseURL != "" {
|
if cfg.APIBaseURL != "" {
|
||||||
opts = append(opts, tgbot.WithServerURL(cfg.APIBaseURL))
|
opts = append(opts, tgbot.WithServerURL(cfg.APIBaseURL))
|
||||||
}
|
}
|
||||||
api, err := tgbot.New(token, opts...)
|
api, err := tgbot.New(cfg.Token, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,6 +75,34 @@ func TestSendTextHasNoMarkup(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getMePathFor captures the path bot.New's getMe call hits for the given TestEnv,
|
||||||
|
// so the test environment routing is covered (a misroute is exactly what makes a
|
||||||
|
// test-environment token fail with "getMe unauthorized").
|
||||||
|
func getMePathFor(t *testing.T, testEnv bool) string {
|
||||||
|
t.Helper()
|
||||||
|
var path string
|
||||||
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if strings.HasSuffix(r.URL.Path, "/getMe") {
|
||||||
|
path = r.URL.Path
|
||||||
|
}
|
||||||
|
io.WriteString(w, `{"ok":true,"result":{"id":1,"is_bot":true,"first_name":"t","username":"tb"}}`)
|
||||||
|
}))
|
||||||
|
t.Cleanup(srv.Close)
|
||||||
|
if _, err := New(Config{Token: "123:ABC", APIBaseURL: srv.URL, TestEnv: testEnv, MiniAppURL: "https://example.com/"}, zap.NewNop()); err != nil {
|
||||||
|
t.Fatalf("new bot (testEnv=%v): %v", testEnv, err)
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTestEnvironmentRoutesGetMe(t *testing.T) {
|
||||||
|
if got, want := getMePathFor(t, true), "/bot123:ABC/test/getMe"; got != want {
|
||||||
|
t.Errorf("TestEnv getMe path = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
if got, want := getMePathFor(t, false), "/bot123:ABC/getMe"; got != want {
|
||||||
|
t.Errorf("prod getMe path = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestStartPayload(t *testing.T) {
|
func TestStartPayload(t *testing.T) {
|
||||||
cases := map[string]string{
|
cases := map[string]string{
|
||||||
"/start g123": "g123",
|
"/start g123": "g123",
|
||||||
|
|||||||
Reference in New Issue
Block a user