mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
This PR adds a fake SMTP server for scale testing. It collects emails sent during tests, which you can then check using the HTTP API. #### Changes - Added mock SMTP server - Added `coder scaletest smtp` CLI command - Implemented HTTP API endpoints to retrieve messages by email - Added auto-purge to prevent memory issues #### HTTP API Endpoints - `GET /messages?email=<email>` – Get messages sent to an email address - `POST /purge` – Clear all messages from memory The HTTP API parses raw email messages to extract the **date**, **subject**, and **notification ID**. Notification IDs are sent in emails like this: ```html <p> <a href="http://127.0.0.1:3000/settings/notifications?disabled=4e19c0ac-94e1-4532-9515-d1801aa283b2" style="color: #2563eb; text-decoration: none;"> Stop receiving emails like this </a> </p> ``` #### CLI ```bash coder scaletest smtp --host localhost --port 33199 --api-port 8080 --purge-at-count 1000 ``` **Flags:** - `--host`: Host for the mock SMTP and API server (default: localhost) - `--port`: Port for the mock SMTP server (random if not specified) - `--api-port`: Port for the HTTP API server (random if not specified) - `--purge-at-count`: Max number of messages before auto-purging (default: 100000)
113 lines
3.0 KiB
Go
113 lines
3.0 KiB
Go
//go:build !slim
|
|
|
|
package cli
|
|
|
|
import (
|
|
"fmt"
|
|
"os/signal"
|
|
"time"
|
|
|
|
"golang.org/x/xerrors"
|
|
|
|
"cdr.dev/slog"
|
|
"cdr.dev/slog/sloggers/sloghuman"
|
|
"github.com/coder/coder/v2/scaletest/smtpmock"
|
|
"github.com/coder/serpent"
|
|
)
|
|
|
|
func (*RootCmd) scaletestSMTP() *serpent.Command {
|
|
var (
|
|
hostAddress string
|
|
smtpPort int64
|
|
apiPort int64
|
|
purgeAtCount int64
|
|
)
|
|
cmd := &serpent.Command{
|
|
Use: "smtp",
|
|
Short: "Start a mock SMTP server for testing",
|
|
Long: `Start a mock SMTP server with an HTTP API server that can be used to purge
|
|
messages and get messages by email.`,
|
|
Handler: func(inv *serpent.Invocation) error {
|
|
ctx := inv.Context()
|
|
notifyCtx, stop := signal.NotifyContext(ctx, StopSignals...)
|
|
defer stop()
|
|
ctx = notifyCtx
|
|
|
|
logger := slog.Make(sloghuman.Sink(inv.Stderr)).Leveled(slog.LevelInfo)
|
|
config := smtpmock.Config{
|
|
HostAddress: hostAddress,
|
|
SMTPPort: int(smtpPort),
|
|
APIPort: int(apiPort),
|
|
Logger: logger,
|
|
}
|
|
srv := new(smtpmock.Server)
|
|
|
|
if err := srv.Start(ctx, config); err != nil {
|
|
return xerrors.Errorf("start mock SMTP server: %w", err)
|
|
}
|
|
defer func() {
|
|
_ = srv.Stop()
|
|
}()
|
|
|
|
_, _ = fmt.Fprintf(inv.Stdout, "Mock SMTP server started on %s\n", srv.SMTPAddress())
|
|
_, _ = fmt.Fprintf(inv.Stdout, "HTTP API server started on %s\n", srv.APIAddress())
|
|
if purgeAtCount > 0 {
|
|
_, _ = fmt.Fprintf(inv.Stdout, " Auto-purge when message count reaches %d\n", purgeAtCount)
|
|
}
|
|
|
|
ticker := time.NewTicker(10 * time.Second)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
_, _ = fmt.Fprintf(inv.Stdout, "\nTotal messages received since last purge: %d\n", srv.MessageCount())
|
|
return nil
|
|
case <-ticker.C:
|
|
count := srv.MessageCount()
|
|
if count > 0 {
|
|
_, _ = fmt.Fprintf(inv.Stdout, "Messages received: %d\n", count)
|
|
}
|
|
|
|
if purgeAtCount > 0 && int64(count) >= purgeAtCount {
|
|
_, _ = fmt.Fprintf(inv.Stdout, "Message count (%d) reached threshold (%d). Purging...\n", count, purgeAtCount)
|
|
srv.Purge()
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
},
|
|
}
|
|
|
|
cmd.Options = []serpent.Option{
|
|
{
|
|
Flag: "host-address",
|
|
Env: "CODER_SCALETEST_SMTP_HOST_ADDRESS",
|
|
Default: "localhost",
|
|
Description: "Host address to bind the mock SMTP and API servers.",
|
|
Value: serpent.StringOf(&hostAddress),
|
|
},
|
|
{
|
|
Flag: "smtp-port",
|
|
Env: "CODER_SCALETEST_SMTP_PORT",
|
|
Description: "Port for the mock SMTP server. Uses a random port if not specified.",
|
|
Value: serpent.Int64Of(&smtpPort),
|
|
},
|
|
{
|
|
Flag: "api-port",
|
|
Env: "CODER_SCALETEST_SMTP_API_PORT",
|
|
Description: "Port for the HTTP API server. Uses a random port if not specified.",
|
|
Value: serpent.Int64Of(&apiPort),
|
|
},
|
|
{
|
|
Flag: "purge-at-count",
|
|
Env: "CODER_SCALETEST_SMTP_PURGE_AT_COUNT",
|
|
Default: "100000",
|
|
Description: "Maximum number of messages to keep before auto-purging. Set to 0 to disable.",
|
|
Value: serpent.Int64Of(&purgeAtCount),
|
|
},
|
|
}
|
|
|
|
return cmd
|
|
}
|