Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/app-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,14 @@ jobs:
shell: pwsh
run: ./scripts/run-test-lab.ps1

- name: Включить тестовый профиль подключения
shell: pwsh
run: ./scripts/start-local.ps1 -Apps -ConnectionTest

- name: Проверить виджет подключения
shell: pwsh
run: ./scripts/run-test-lab.ps1 -TaskName test:connection

- name: Удалить только тестовый Compose project
if: always()
shell: pwsh
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,16 @@ task test:model
Команда проверяет путь до локальной модели, разбор относительной даты и создание действия, но не
входит в обычный CI: на CPU ответ может занимать десятки секунд.

Чтобы проверить поведение при отсутствии подключения Google Calendar, выполни:

```powershell
task test:connection
```

Команда поднимает отдельный тестовый override только с коннектором `google-calendar` и запускает
сценарий из `test-lab`. Он проверяет виджет подключения, OAuth state, PKCE и свежую ссылку при
повторном запросе. Настоящий Google API не вызывается, реальные credentials не нужны.

Профиль направляет Agent Runtime на `host.docker.internal:11434`, выбирает `qwen2.5:7b` и увеличивает
тайм-ауты только на пути до модели. Значения можно переопределить в локальном `.env`. Ollama и модель
не входят в Compose: разработчик управляет ими отдельно, а CI остаётся быстрым и воспроизводимым.
Expand Down
6 changes: 6 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ tasks:
- task: services:model:up
- '{{.POWERSHELL}} {{.POWERSHELL_ARGS}} -File ./scripts/run-test-lab.ps1 -TaskName test:model'

test:connection:
desc: Поднять тестовый Google OAuth профиль и проверить виджет подключения
cmds:
- '{{.POWERSHELL}} {{.POWERSHELL_ARGS}} -File ./scripts/start-local.ps1 -Apps -ConnectionTest'
- '{{.POWERSHELL}} {{.POWERSHELL_ARGS}} -File ./scripts/run-test-lab.ps1 -TaskName test:connection'

verify:
desc: Выполнить локальные проверки конфигурации и документации
cmds:
Expand Down
15 changes: 15 additions & 0 deletions compose/connection-test.local.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
services:
conversation-service:
environment:
AVAILABLE_CONNECTORS: google-calendar
CONNECTION_URL: http://connection-service:8080
depends_on:
connection-service:
condition: service_healthy

connection-service:
environment:
GOOGLE_OAUTH_ENABLED: "true"
GOOGLE_OAUTH_CLIENT_ID: portable-agent-connection-test
GOOGLE_OAUTH_CLIENT_SECRET: local-test-value-not-a-real-secret
GOOGLE_OAUTH_REDIRECT_URI: https://portable-agent.test/api/v1/connections/callback
10 changes: 5 additions & 5 deletions config/versions.env
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ LOKI_IMAGE=grafana/loki:3.7.7
BUSYBOX_IMAGE=busybox:1.37.0
WIREMOCK_IMAGE=wiremock/wiremock:3.13.2
CLOUDFLARED_IMAGE=cloudflare/cloudflared@sha256:072c067d25ccbe61d46e18f0d0723255f2bb5304f7317caa95b27031520ff92c
CHANNEL_GATEWAY_IMAGE=ghcr.io/portable-agent/channel-gateway:c1f8dbf209d641008a627230d4d793d4454cbaf3
CHANNEL_GATEWAY_IMAGE=ghcr.io/portable-agent/channel-gateway:1ab140f73121e256b55e749468f010dd1fc57b3b
AGENT_RUNTIME_IMAGE=ghcr.io/portable-agent/agent-runtime:f50ea7d5c722c93df1519e7ac1d12d210f439726
ACTION_SERVICE_IMAGE=ghcr.io/portable-agent/action-service:74152ac0a8832a8879126db2c500ae555c9b77f7
CONVERSATION_SERVICE_IMAGE=ghcr.io/portable-agent/conversation-service:fb069a35c6a8406f88f665e8197ebfbc3564008a
CONVERSATION_SERVICE_IMAGE=ghcr.io/portable-agent/conversation-service:952204ae41f403763870ce4e2d532ab0affa7c13
MCP_GATEWAY_IMAGE=ghcr.io/portable-agent/mcp-gateway:922390b293345f7483dcdee20d5020f417bd7f39
CALENDAR_MCP_IMAGE=ghcr.io/portable-agent/calendar-mcp:30b869b4f9f1c23a4be59e8cf5a3b3a3d3c89799
CONNECTION_SERVICE_IMAGE=ghcr.io/portable-agent/connection-service:f86ff7b1b3fa4da16ff580d5e36f29eccdbf1c1a
TELEGRAM_ADAPTER_IMAGE=ghcr.io/portable-agent/telegram-adapter:c2be5c859cb7b3ddb8fae4b3561979b8db611e9e
TEST_LAB_REF=6969e1bbae930146cd149276167137ff74fb2e98
CONNECTION_SERVICE_IMAGE=ghcr.io/portable-agent/connection-service:582f144f0db21806595723bf0f931e53a73b2071
TELEGRAM_ADAPTER_IMAGE=ghcr.io/portable-agent/telegram-adapter:bd9d19a0154340524438a16638ecf308873a5adf
TEST_LAB_REF=39ef3dfbb6156e2f4b0df0a3b38cb9e327568f21
6 changes: 6 additions & 0 deletions docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ Taskfile — единая точка входа. Внутренние PowerShell
Команда `task test:model` сама поднимает этот профиль и запускает отдельный opt-in сценарий из
`test-lab`; быстрый `task test:e2e` по-прежнему использует детерминированную demo-модель.

Команда `task test:connection` поднимает приложения с override
`compose/connection-test.local.yaml`. В нём Conversation Service предлагает только
`google-calendar`, а Connection Service строит ссылку с тестовой OAuth-конфигурацией. Сценарий не
обращается к Google: он проверяет контракт виджета, state, PKCE и выдачу свежей ссылки. Реальные
credentials по-прежнему хранятся только в локальном `.env` и для этой проверки не нужны.

Допустимые имена: `channel-gateway`, `conversation-service`, `agent-runtime`, `action-service`,
`mcp-gateway`, `calendar-mcp`, `google-calendar-mcp`, `connection-service` и `telegram-adapter`.
Неизвестное имя отклоняется до вызова Docker.
Expand Down
11 changes: 11 additions & 0 deletions docs/runbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,17 @@ Channel Gateway, создаёт действие со случайным `reques
Action Service с событием в тестовом календарном коннекторе. Если `test-lab` лежит в другой папке,
задай `TEST_LAB_PATH` в `.env`.

Путь запроса подключения внешнего календаря проверяется отдельно:

```powershell
task test:connection
```

Сценарий ожидает виджет `connection` со ссылкой Google, state и PKCE. Если вместо него вернулся
виджет подтверждения, проверь, что команда запустила `compose/connection-test.local.yaml`, а в
Conversation Service значение `AVAILABLE_CONNECTORS` равно `google-calendar`. В чистом CI база
Connection Service пуста; локальное уже созданное подключение может изменить ожидаемый ответ.

Первый Docker build скачивает Gradle и Python packages и может быть заметно медленнее повторных
запусков. BuildKit сохраняет слои для следующих сборок.

Expand Down
22 changes: 20 additions & 2 deletions scripts/check-compose.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ foreach ($taskName in @(
"doctor",
"status",
"test:e2e",
"test:connection",
"test:model",
"down",
"reset"
Expand All @@ -245,6 +246,10 @@ if ($taskfile -notmatch 'scripts/start-local\.ps1' `
if ($startScript -notmatch '\[string\]\$Service' -or $startScript -notmatch '\$Service') {
throw "start-local должен уметь запускать один выбранный сервис."
}
if ($startScript -notmatch '\[switch\]\$ConnectionTest' `
-or $startScript -notmatch 'compose/connection-test.local.yaml') {
throw "start-local должен поддерживать изолированный connection acceptance профиль."
}
$serviceScriptPath = "scripts/service-local.ps1"
if (-not (Test-Path -LiteralPath $serviceScriptPath)) {
throw "Нет безопасного управления отдельным локальным сервисом."
Expand Down Expand Up @@ -282,11 +287,21 @@ $testLabRunner = Get-Content -Raw -LiteralPath $testLabRunnerPath
if ($testLabRunner -notmatch '\$containerIds\s*=\s*@\(\s*\r?\n\s*\(& docker ps') {
throw "Результат docker ps должен сохраняться массивом до обращения по индексу."
}
foreach ($required in @("TEST_LAB_PATH", "portable-agent-realm.json", "CALENDAR_TEST_API_KEY", "DOCKER_NETWORK", "docker inspect", "com.docker.compose.service=channel-gateway", "http://channel-gateway:8080", "http://action-service:8080", "http://calendar-mcp:8080", "task", "test:e2e", "test:model", "TaskName")) {
foreach ($required in @("TEST_LAB_PATH", "portable-agent-realm.json", "CALENDAR_TEST_API_KEY", "DOCKER_NETWORK", "docker inspect", "com.docker.compose.service=channel-gateway", "http://channel-gateway:8080", "http://action-service:8080", "http://calendar-mcp:8080", "task", "test:e2e", "test:model", "test:connection", "TaskName")) {
if ($testLabRunner -notmatch [regex]::Escape($required)) {
throw "Адаптер test-lab не содержит $required."
}
}
$connectionOverridePath = "compose/connection-test.local.yaml"
if (-not (Test-Path -LiteralPath $connectionOverridePath)) {
throw "Нет Compose override для connection acceptance."
}
$connectionOverride = Get-Content -Raw -LiteralPath $connectionOverridePath
foreach ($required in @("AVAILABLE_CONNECTORS", "google-calendar", "GOOGLE_OAUTH_ENABLED", "GOOGLE_OAUTH_CLIENT_ID", "GOOGLE_OAUTH_CLIENT_SECRET", "GOOGLE_OAUTH_REDIRECT_URI")) {
if ($connectionOverride -notmatch [regex]::Escape($required)) {
throw "Connection acceptance override не содержит $required."
}
}
$versions = Get-Content -Raw -LiteralPath "config/versions.env"
if ($versions -notmatch '(?m)^CLOUDFLARED_IMAGE=cloudflare/cloudflared@sha256:[0-9a-f]{64}\r?$') {
throw "Cloudflared image должен быть закреплён digest."
Expand Down Expand Up @@ -380,7 +395,7 @@ if (-not (Test-Path -LiteralPath $appWorkflowPath)) {
throw "Нет CI-проверки полного Compose-среза."
}
$appWorkflow = Get-Content -Raw -LiteralPath $appWorkflowPath
foreach ($required in @("versions.env", "channel-gateway", "agent-runtime", "CONVERSATION_SERVICE_CONTEXT", "repository: portable-agent/conversation-service", 'ref: ${{ steps.versions.outputs.conversation }}', "CONNECTION_SERVICE_CONTEXT", "repository: portable-agent/connection-service", 'ref: ${{ steps.versions.outputs.connection }}', "TELEGRAM_ADAPTER_CONTEXT", "repository: portable-agent/telegram-adapter", 'ref: ${{ steps.versions.outputs.telegram }}', "repository: portable-agent/test-lab", 'ref: ${{ steps.versions.outputs.test_lab }}', "start-local.ps1 -Apps", "run-test-lab.ps1", "stop-local.ps1 -DeleteData", "if: always()")) {
foreach ($required in @("versions.env", "channel-gateway", "agent-runtime", "CONVERSATION_SERVICE_CONTEXT", "repository: portable-agent/conversation-service", 'ref: ${{ steps.versions.outputs.conversation }}', "CONNECTION_SERVICE_CONTEXT", "repository: portable-agent/connection-service", 'ref: ${{ steps.versions.outputs.connection }}', "TELEGRAM_ADAPTER_CONTEXT", "repository: portable-agent/telegram-adapter", 'ref: ${{ steps.versions.outputs.telegram }}', "repository: portable-agent/test-lab", 'ref: ${{ steps.versions.outputs.test_lab }}', "start-local.ps1 -Apps", "ConnectionTest", "test:connection", "run-test-lab.ps1", "stop-local.ps1 -DeleteData", "if: always()")) {
if ($appWorkflow -notmatch [regex]::Escape($required)) {
throw "CI-проверка полного среза не содержит $required."
}
Expand All @@ -394,5 +409,8 @@ if ($LASTEXITCODE -ne 0) { throw "Compose config содержит ошибку."
& docker compose --env-file .env.example --env-file config/versions.env -f compose/compose.yaml `
-f compose/apps.local.yaml -f compose/model.local.yaml --profile core --profile apps config --quiet
if ($LASTEXITCODE -ne 0) { throw "Compose config локальной AI-модели содержит ошибку." }
& docker compose --env-file .env.example --env-file config/versions.env -f compose/compose.yaml `
-f compose/apps.local.yaml -f compose/connection-test.local.yaml --profile core --profile apps config --quiet
if ($LASTEXITCODE -ne 0) { throw "Compose config connection acceptance содержит ошибку." }
Write-Host "Compose config прошёл проверку."

2 changes: 1 addition & 1 deletion scripts/run-test-lab.ps1
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
param(
[string]$TestLabPath = "",
[ValidateSet("test:e2e", "test:model")]
[ValidateSet("test:e2e", "test:model", "test:connection")]
[string]$TaskName = "test:e2e"
)

Expand Down
5 changes: 5 additions & 0 deletions scripts/start-local.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
[switch]$Observe,
[switch]$Apps,
[switch]$Model,
[switch]$ConnectionTest,
[ValidateSet("channel-gateway", "agent-runtime", "action-service", "conversation-service", "mcp-gateway", "calendar-mcp", "google-calendar-mcp", "connection-service", "telegram-adapter")]
[string]$Service
)
Expand All @@ -10,6 +11,9 @@ if ($Apps -and $Service) { throw "Используй -Apps или -Service, но
if ($Model -and -not ($Apps -or $Service)) {
throw "Используй -Model вместе с -Apps или -Service."
}
if ($ConnectionTest -and -not ($Apps -or $Service)) {
throw "Используй -ConnectionTest вместе с -Apps или -Service."
}
. "$PSScriptRoot/local-settings.ps1"
$envFiles = @("--env-file", ".env.example", "--env-file", "config/versions.env")
if (Test-Path .env) { $envFiles += @("--env-file", ".env") }
Expand All @@ -18,6 +22,7 @@ $runningOnWindows = $PSVersionTable.PSEdition -eq "Desktop" -or $IsWindows
if ($runningOnWindows) { $composeFiles += @("-f", "compose/windows.local.yaml") }
$appComposeFiles = @($composeFiles + @("-f", "compose/apps.local.yaml"))
if ($Model) { $appComposeFiles += @("-f", "compose/model.local.yaml") }
if ($ConnectionTest) { $appComposeFiles += @("-f", "compose/connection-test.local.yaml") }
Initialize-LocalTelegramKey
Initialize-LocalConnectionKey
$coreProfiles = @("--profile", "core")
Expand Down
Loading