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
18 changes: 17 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:

e2e:
runs-on: ubuntu-latest
timeout-minutes: 15
timeout-minutes: 20
permissions:
contents: write
pull-requests: write
Expand Down Expand Up @@ -65,6 +65,11 @@ jobs:
- name: Install dependencies
run: npm ci

- name: Install Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_wrapper: false

- name: Install Playwright
run: npx playwright install --with-deps chromium

Expand All @@ -80,8 +85,19 @@ jobs:
id: e2e
continue-on-error: true
env:
# Cloud infrastructure credentials (from repo secrets with E2E_ prefix)
CLOUDFLARE_API_TOKEN: ${{ secrets.E2E_CLOUDFLARE_API_TOKEN }}
CF_ACCOUNT_ID: ${{ secrets.E2E_CF_ACCOUNT_ID }}
WORKERS_SUBDOMAIN: ${{ secrets.E2E_WORKERS_SUBDOMAIN }}
CF_ACCESS_TEAM_DOMAIN: ${{ secrets.E2E_CF_ACCESS_TEAM_DOMAIN }}
R2_ACCESS_KEY_ID: ${{ secrets.E2E_R2_ACCESS_KEY_ID }}
R2_SECRET_ACCESS_KEY: ${{ secrets.E2E_R2_SECRET_ACCESS_KEY }}
# AI provider (optional, for chat tests)
AI_GATEWAY_API_KEY: ${{ secrets.AI_GATEWAY_API_KEY }}
AI_GATEWAY_BASE_URL: ${{ secrets.AI_GATEWAY_BASE_URL }}
# Unique test run ID for parallel isolation
E2E_TEST_RUN_ID: ${{ github.run_id }}-${{ matrix.config.name }}
# Matrix-specific config
TELEGRAM_BOT_TOKEN: ${{ matrix.config.env.TELEGRAM_BOT_TOKEN }}
TELEGRAM_DM_POLICY: ${{ matrix.config.env.TELEGRAM_DM_POLICY }}
DISCORD_BOT_TOKEN: ${{ matrix.config.env.DISCORD_BOT_TOKEN }}
Expand Down
15 changes: 14 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,17 @@ Thumbs.db
*.greger

# playwright-cli
.playwright-cli/
.playwright-cli/

# Terraform
*.tfstate
*.tfstate.*
.terraform/
.terraform.lock.hcl
terraform.tfvars

# E2E test credentials
test/e2e/.dev.vars

# Temporary e2e wrangler configs
.wrangler-e2e-*.jsonc
9 changes: 7 additions & 2 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,10 @@ export const STARTUP_TIMEOUT_MS = 180_000;
/** Mount path for R2 persistent storage inside the container */
export const R2_MOUNT_PATH = '/data/moltbot';

/** R2 bucket name for persistent storage */
export const R2_BUCKET_NAME = 'moltbot-data';
/**
* R2 bucket name for persistent storage.
* Can be overridden via R2_BUCKET_NAME env var for test isolation.
*/
export function getR2BucketName(env?: { R2_BUCKET_NAME?: string }): string {
return env?.R2_BUCKET_NAME || 'moltbot-data';
}
19 changes: 19 additions & 0 deletions src/gateway/r2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,25 @@ describe('mountR2Storage', () => {
);
});

it('uses custom bucket name from R2_BUCKET_NAME env var', async () => {
const { sandbox, mountBucketMock } = createMockSandbox({ mounted: false });
const env = createMockEnvWithR2({
R2_ACCESS_KEY_ID: 'key123',
R2_SECRET_ACCESS_KEY: 'secret',
CF_ACCOUNT_ID: 'account123',
R2_BUCKET_NAME: 'moltbot-e2e-test123',
});

const result = await mountR2Storage(sandbox, env);

expect(result).toBe(true);
expect(mountBucketMock).toHaveBeenCalledWith(
'moltbot-e2e-test123',
'/data/moltbot',
expect.any(Object)
);
});

it('returns true immediately when bucket is already mounted', async () => {
const { sandbox, mountBucketMock } = createMockSandbox({ mounted: true });
const env = createMockEnvWithR2();
Expand Down
7 changes: 4 additions & 3 deletions src/gateway/r2.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Sandbox } from '@cloudflare/sandbox';
import type { MoltbotEnv } from '../types';
import { R2_MOUNT_PATH, R2_BUCKET_NAME } from '../config';
import { R2_MOUNT_PATH, getR2BucketName } from '../config';

/**
* Check if R2 is already mounted by looking at the mount table
Expand Down Expand Up @@ -45,9 +45,10 @@ export async function mountR2Storage(sandbox: Sandbox, env: MoltbotEnv): Promise
return true;
}

const bucketName = getR2BucketName(env);
try {
console.log('Mounting R2 bucket at', R2_MOUNT_PATH);
await sandbox.mountBucket(R2_BUCKET_NAME, R2_MOUNT_PATH, {
console.log('Mounting R2 bucket', bucketName, 'at', R2_MOUNT_PATH);
await sandbox.mountBucket(bucketName, R2_MOUNT_PATH, {
endpoint: `https://${env.CF_ACCOUNT_ID}.r2.cloudflarestorage.com`,
// Pass credentials explicitly since we use R2_* naming instead of AWS_*
credentials: {
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export interface MoltbotEnv {
// R2 credentials for bucket mounting (set via wrangler secret)
R2_ACCESS_KEY_ID?: string;
R2_SECRET_ACCESS_KEY?: string;
R2_BUCKET_NAME?: string; // Override bucket name (default: 'moltbot-data')
CF_ACCOUNT_ID?: string; // Cloudflare account ID for R2 endpoint
// Browser Rendering binding for CDP shim
BROWSER?: Fetcher;
Expand Down
120 changes: 120 additions & 0 deletions test/e2e/.dev.vars.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# Cloud E2E Test Credentials
# Copy this file to .dev.vars and fill in your values
# DO NOT commit .dev.vars to git!

# =============================================================================
# CLOUDFLARE_API_TOKEN
# =============================================================================
# Required: Cloudflare API token with specific permissions for e2e tests.
#
# How to create:
# 1. Go to https://dash.cloudflare.com/profile/api-tokens
# 2. Click "Create Token"
# 3. Click "Create Custom Token" (at the bottom)
# 4. Configure the token:
#
# Token name: moltworker-e2e-tests (or whatever you prefer)
#
# Permissions (add all of these):
# ┌─────────────────────────────────────────────────────────────────────────┐
# │ Account │ Workers Scripts │ Edit │
# │ Account │ Workers R2 Storage │ Edit │
# │ Account │ Cloudflare Containers │ Edit │
# │ Account │ Access: Apps and Policies │ Edit │
# │ Account │ Access: Service Tokens │ Edit │
# └─────────────────────────────────────────────────────────────────────────┘
#
# Account Resources:
# - Include: Your account (or "All accounts" if you have multiple)
#
# Client IP Address Filtering: (optional, leave blank for no restrictions)
#
# TTL: (optional, set an expiry if desired)
#
# 5. Click "Continue to summary"
# 6. Click "Create Token"
# 7. Copy the token value (you won't see it again!)
#
CLOUDFLARE_API_TOKEN=

# =============================================================================
# CF_ACCOUNT_ID
# =============================================================================
# Required: Your Cloudflare account ID
#
# How to find:
# 1. Go to https://dash.cloudflare.com/
# 2. Click the "..." menu next to your account name in the sidebar
# 3. Click "Copy Account ID"
#
# Or: Dashboard → any zone → Overview → scroll down to "API" section
#
CF_ACCOUNT_ID=

# =============================================================================
# WORKERS_SUBDOMAIN
# =============================================================================
# Required: Your workers.dev subdomain
#
# This is the subdomain part of your workers.dev URL.
# For example, if your workers deploy to "my-worker.myaccount.workers.dev",
# then your WORKERS_SUBDOMAIN is "myaccount".
#
# How to find:
# 1. Go to https://dash.cloudflare.com/ → Workers & Pages
# 2. Look at any deployed worker's URL, or
# 3. Go to Workers & Pages → Overview → your subdomain is shown at the top
#
WORKERS_SUBDOMAIN=

# =============================================================================
# CF_ACCESS_TEAM_DOMAIN
# =============================================================================
# Required: Your Cloudflare Access team domain
#
# This is your Zero Trust organization's domain, typically in the format:
# "yourteam.cloudflareaccess.com"
#
# How to find:
# 1. Go to https://one.dash.cloudflare.com/ (Zero Trust dashboard)
# 2. Go to Settings → Custom Pages
# 3. Your team domain is shown at the top (e.g., "yourteam.cloudflareaccess.com")
#
# Or: Look at any Access login page URL - it will be https://yourteam.cloudflareaccess.com/...
#
CF_ACCESS_TEAM_DOMAIN=

# =============================================================================
# R2_ACCESS_KEY_ID and R2_SECRET_ACCESS_KEY
# =============================================================================
# Required: R2 API credentials for bucket mounting inside the container
#
# How to create:
# 1. Go to https://dash.cloudflare.com/ → R2 → Overview
# 2. Click "Manage R2 API Tokens" (top right)
# 3. Click "Create API Token"
# 4. Configure:
# - Token name: moltworker-e2e (or whatever you prefer)
# - Permissions: Object Read & Write
# - Specify bucket(s): You can leave as "Apply to all buckets" or
# limit to buckets starting with "moltbot-" for safety
# - TTL: (optional)
# 5. Click "Create API Token"
# 6. Copy both the "Access Key ID" and "Secret Access Key"
# (Secret is only shown once!)
#
R2_ACCESS_KEY_ID=
R2_SECRET_ACCESS_KEY=

# =============================================================================
# OPTIONAL SETTINGS
# =============================================================================

# Unique test run ID for isolation (default: "local")
# In CI, set this to the PR number or a unique identifier to allow parallel runs
# E2E_TEST_RUN_ID=local

# AI provider credentials (at least one recommended for chat/conversation tests)
# AI_GATEWAY_API_KEY=
# AI_GATEWAY_BASE_URL=
# ANTHROPIC_API_KEY=
Loading