mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-01 23:15:27 +08:00
feat: Improve configuration loading robustness, add Vercel-specific fallbacks, and update documentation for config.json best practices.
This commit is contained in:
@@ -52,6 +52,9 @@ DS2API_ADMIN_KEY=admin
|
||||
|
||||
# Option C: Base64 encoded JSON (recommended for Vercel env var)
|
||||
# DS2API_CONFIG_JSON=eyJrZXlzIjpbInlvdXItYXBpLWtleSJdLCJhY2NvdW50cyI6W3siZW1haWwiOiJ1c2VyQGV4YW1wbGUuY29tIiwicGFzc3dvcmQiOiJ4eHgiLCJ0b2tlbiI6IiJ9XX0=
|
||||
#
|
||||
# Generate from local config.json:
|
||||
# DS2API_CONFIG_JSON="$(base64 < config.json | tr -d '\n')"
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# Paths (optional)
|
||||
|
||||
23
API.en.md
23
API.en.md
@@ -9,6 +9,7 @@ This document describes the actual behavior of the current Go codebase.
|
||||
## Table of Contents
|
||||
|
||||
- [Basics](#basics)
|
||||
- [Configuration Best Practice](#configuration-best-practice)
|
||||
- [Authentication](#authentication)
|
||||
- [Route Index](#route-index)
|
||||
- [Health Endpoints](#health-endpoints)
|
||||
@@ -31,6 +32,28 @@ This document describes the actual behavior of the current Go codebase.
|
||||
|
||||
---
|
||||
|
||||
## Configuration Best Practice
|
||||
|
||||
Use `config.json` as the single source of truth:
|
||||
|
||||
```bash
|
||||
cp config.example.json config.json
|
||||
# Edit config.json (keys/accounts)
|
||||
```
|
||||
|
||||
Use it per deployment mode:
|
||||
|
||||
- Local run: read `config.json` directly
|
||||
- Docker / Vercel: generate Base64 from `config.json`, then set `DS2API_CONFIG_JSON`
|
||||
|
||||
```bash
|
||||
DS2API_CONFIG_JSON="$(base64 < config.json | tr -d '\n')"
|
||||
```
|
||||
|
||||
For Vercel one-click bootstrap, you can set only `DS2API_ADMIN_KEY` first, then import config at `/admin` and sync env vars from the "Vercel Sync" page.
|
||||
|
||||
---
|
||||
|
||||
## Authentication
|
||||
|
||||
### Business Endpoints (`/v1/*`, `/anthropic/*`)
|
||||
|
||||
23
API.md
23
API.md
@@ -9,6 +9,7 @@
|
||||
## 目录
|
||||
|
||||
- [基础信息](#基础信息)
|
||||
- [配置最佳实践](#配置最佳实践)
|
||||
- [鉴权规则](#鉴权规则)
|
||||
- [路由总览](#路由总览)
|
||||
- [健康检查](#健康检查)
|
||||
@@ -31,6 +32,28 @@
|
||||
|
||||
---
|
||||
|
||||
## 配置最佳实践
|
||||
|
||||
推荐把 `config.json` 作为唯一配置源:
|
||||
|
||||
```bash
|
||||
cp config.example.json config.json
|
||||
# 编辑 config.json(keys/accounts)
|
||||
```
|
||||
|
||||
按部署方式使用:
|
||||
|
||||
- 本地运行:直接读取 `config.json`
|
||||
- Docker / Vercel:从 `config.json` 生成 Base64,填入 `DS2API_CONFIG_JSON`
|
||||
|
||||
```bash
|
||||
DS2API_CONFIG_JSON="$(base64 < config.json | tr -d '\n')"
|
||||
```
|
||||
|
||||
Vercel 一键部署可先只填 `DS2API_ADMIN_KEY`,部署后在 `/admin` 导入配置,再通过 “Vercel 同步” 写回环境变量。
|
||||
|
||||
---
|
||||
|
||||
## 鉴权规则
|
||||
|
||||
### 业务接口(`/v1/*`、`/anthropic/*`)
|
||||
|
||||
59
DEPLOY.en.md
59
DEPLOY.en.md
@@ -33,6 +33,17 @@ Config source (choose one):
|
||||
- **File**: `config.json` (recommended for local/Docker)
|
||||
- **Environment variable**: `DS2API_CONFIG_JSON` (recommended for Vercel; supports raw JSON or Base64)
|
||||
|
||||
Unified recommendation (best practice):
|
||||
|
||||
```bash
|
||||
cp config.example.json config.json
|
||||
# Edit config.json
|
||||
```
|
||||
|
||||
Use `config.json` as the single source of truth:
|
||||
- Local run: read `config.json` directly
|
||||
- Docker / Vercel: generate `DS2API_CONFIG_JSON` (Base64) from `config.json` and inject it
|
||||
|
||||
---
|
||||
|
||||
## 1. Local Run
|
||||
@@ -99,11 +110,15 @@ go build -o ds2api ./cmd/ds2api
|
||||
### 2.1 Basic Steps
|
||||
|
||||
```bash
|
||||
# Copy and edit environment
|
||||
# Copy env template
|
||||
cp .env.example .env
|
||||
# Edit .env, at minimum set:
|
||||
|
||||
# Generate single-line Base64 from config.json
|
||||
DS2API_CONFIG_JSON="$(base64 < config.json | tr -d '\n')"
|
||||
|
||||
# Edit .env and set:
|
||||
# DS2API_ADMIN_KEY=your-admin-key
|
||||
# DS2API_CONFIG_JSON={"keys":[...],"accounts":[...]}
|
||||
# DS2API_CONFIG_JSON=${DS2API_CONFIG_JSON}
|
||||
|
||||
# Start
|
||||
docker-compose up -d
|
||||
@@ -167,15 +182,49 @@ If container logs look normal but the admin panel is unreachable, check these fi
|
||||
|
||||
1. **Fork** the repo to your GitHub account
|
||||
2. **Import** the project on Vercel
|
||||
3. **Set environment variables** (at minimum):
|
||||
3. **Set environment variables** (minimum required: one variable):
|
||||
|
||||
| Variable | Description |
|
||||
| --- | --- |
|
||||
| `DS2API_ADMIN_KEY` | Admin key (required) |
|
||||
| `DS2API_CONFIG_JSON` | Config content, raw JSON or Base64 (required) |
|
||||
| `DS2API_CONFIG_JSON` | Config content, raw JSON or Base64 (optional, recommended) |
|
||||
|
||||
4. **Deploy**
|
||||
|
||||
### 3.1.1 Recommended Input (avoid `DS2API_CONFIG_JSON` mistakes)
|
||||
|
||||
If you prefer faster one-click bootstrap, you can leave `DS2API_CONFIG_JSON` empty first, then open `/admin` after deployment, import config, and sync it back to Vercel env vars from the "Vercel Sync" page.
|
||||
|
||||
Recommended: in repo root, copy the template first and fill your real accounts:
|
||||
|
||||
```bash
|
||||
cp config.example.json config.json
|
||||
# Edit config.json
|
||||
```
|
||||
|
||||
Do not hand-edit large JSON directly in Vercel. Generate Base64 locally and paste it:
|
||||
|
||||
```bash
|
||||
# Run in repo root
|
||||
DS2API_CONFIG_JSON="$(base64 < config.json | tr -d '\n')"
|
||||
echo "$DS2API_CONFIG_JSON"
|
||||
```
|
||||
|
||||
If you choose to preconfigure before first deploy, set these vars in Vercel Project Settings -> Environment Variables:
|
||||
|
||||
```text
|
||||
DS2API_ADMIN_KEY=replace-with-a-strong-secret
|
||||
DS2API_CONFIG_JSON=<the single-line Base64 output above>
|
||||
```
|
||||
|
||||
Optional but recommended (for WebUI one-click Vercel sync):
|
||||
|
||||
```text
|
||||
VERCEL_TOKEN=your-vercel-token
|
||||
VERCEL_PROJECT_ID=prj_xxxxxxxxxxxx
|
||||
VERCEL_TEAM_ID=team_xxxxxxxxxxxx # optional for personal accounts
|
||||
```
|
||||
|
||||
### 3.2 Optional Environment Variables
|
||||
|
||||
| Variable | Description | Default |
|
||||
|
||||
59
DEPLOY.md
59
DEPLOY.md
@@ -33,6 +33,17 @@
|
||||
- **文件方式**:`config.json`(推荐本地/Docker 使用)
|
||||
- **环境变量方式**:`DS2API_CONFIG_JSON`(推荐 Vercel 使用,支持 JSON 字符串或 Base64 编码)
|
||||
|
||||
统一建议(最优实践):
|
||||
|
||||
```bash
|
||||
cp config.example.json config.json
|
||||
# 编辑 config.json
|
||||
```
|
||||
|
||||
建议把 `config.json` 作为唯一配置源:
|
||||
- 本地运行:直接读 `config.json`
|
||||
- Docker / Vercel:从 `config.json` 生成 `DS2API_CONFIG_JSON`(Base64)注入环境变量
|
||||
|
||||
---
|
||||
|
||||
## 一、本地运行
|
||||
@@ -99,11 +110,15 @@ go build -o ds2api ./cmd/ds2api
|
||||
### 2.1 基本步骤
|
||||
|
||||
```bash
|
||||
# 复制并编辑环境变量
|
||||
# 复制环境变量模板
|
||||
cp .env.example .env
|
||||
# 编辑 .env,至少设置:
|
||||
|
||||
# 从 config.json 生成单行 Base64
|
||||
DS2API_CONFIG_JSON="$(base64 < config.json | tr -d '\n')"
|
||||
|
||||
# 编辑 .env(请改成你的强密码),设置:
|
||||
# DS2API_ADMIN_KEY=your-admin-key
|
||||
# DS2API_CONFIG_JSON={"keys":[...],"accounts":[...]}
|
||||
# DS2API_CONFIG_JSON=${DS2API_CONFIG_JSON}
|
||||
|
||||
# 启动
|
||||
docker-compose up -d
|
||||
@@ -167,15 +182,49 @@ healthcheck:
|
||||
|
||||
1. **Fork 仓库**到你的 GitHub 账号
|
||||
2. **在 Vercel 上导入项目**
|
||||
3. **配置环境变量**(至少设置以下两项):
|
||||
3. **配置环境变量**(最少只需设置以下一项):
|
||||
|
||||
| 变量 | 说明 |
|
||||
| --- | --- |
|
||||
| `DS2API_ADMIN_KEY` | 管理密钥(必填) |
|
||||
| `DS2API_CONFIG_JSON` | 配置内容,JSON 字符串或 Base64 编码(必填) |
|
||||
| `DS2API_CONFIG_JSON` | 配置内容,JSON 字符串或 Base64 编码(可选,建议) |
|
||||
|
||||
4. **部署**
|
||||
|
||||
### 3.1.1 推荐填写方式(避免 `DS2API_CONFIG_JSON` 填错)
|
||||
|
||||
如果你想先完成一键部署,也可以先不填 `DS2API_CONFIG_JSON`,部署后进入 `/admin` 导入配置,再在「Vercel 同步」里写回环境变量。
|
||||
|
||||
建议先在仓库目录复制示例配置,再按实际账号填写:
|
||||
|
||||
```bash
|
||||
cp config.example.json config.json
|
||||
# 编辑 config.json
|
||||
```
|
||||
|
||||
不要在 Vercel 面板里手写复杂 JSON,建议本地生成 Base64 后粘贴:
|
||||
|
||||
```bash
|
||||
# 在仓库根目录执行
|
||||
DS2API_CONFIG_JSON="$(base64 < config.json | tr -d '\n')"
|
||||
echo "$DS2API_CONFIG_JSON"
|
||||
```
|
||||
|
||||
如果你选择在部署前就预置配置,请在 Vercel Project Settings -> Environment Variables 配置:
|
||||
|
||||
```text
|
||||
DS2API_ADMIN_KEY=请替换为强密码
|
||||
DS2API_CONFIG_JSON=上一步生成的一整行 Base64
|
||||
```
|
||||
|
||||
可选但推荐(用于 WebUI 一键同步 Vercel 配置):
|
||||
|
||||
```text
|
||||
VERCEL_TOKEN=你的 Vercel Token
|
||||
VERCEL_PROJECT_ID=prj_xxxxxxxxxxxx
|
||||
VERCEL_TEAM_ID=team_xxxxxxxxxxxx # 个人账号可留空
|
||||
```
|
||||
|
||||
### 3.2 可选环境变量
|
||||
|
||||
| 变量 | 说明 | 默认值 |
|
||||
|
||||
42
README.MD
42
README.MD
@@ -88,6 +88,19 @@ flowchart LR
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 通用第一步(所有部署方式)
|
||||
|
||||
把 `config.json` 作为唯一配置源(推荐做法):
|
||||
|
||||
```bash
|
||||
cp config.example.json config.json
|
||||
# 编辑 config.json
|
||||
```
|
||||
|
||||
后续部署建议:
|
||||
- 本地运行:直接读取 `config.json`
|
||||
- Docker / Vercel:由 `config.json` 生成 `DS2API_CONFIG_JSON`(Base64)注入环境变量
|
||||
|
||||
### 方式一:本地运行
|
||||
|
||||
**前置要求**:Go 1.24+,Node.js 20+(仅在需要构建 WebUI 时)
|
||||
@@ -112,14 +125,20 @@ go run ./cmd/ds2api
|
||||
### 方式二:Docker 运行
|
||||
|
||||
```bash
|
||||
# 1. 配置环境变量
|
||||
# 1. 准备环境变量文件
|
||||
cp .env.example .env
|
||||
# 编辑 .env
|
||||
|
||||
# 2. 启动
|
||||
# 2. 从 config.json 生成 DS2API_CONFIG_JSON(单行 Base64)
|
||||
DS2API_CONFIG_JSON="$(base64 < config.json | tr -d '\n')"
|
||||
|
||||
# 3. 编辑 .env,设置:
|
||||
# DS2API_ADMIN_KEY=请替换为强密码
|
||||
# DS2API_CONFIG_JSON=${DS2API_CONFIG_JSON}
|
||||
|
||||
# 4. 启动
|
||||
docker-compose up -d
|
||||
|
||||
# 3. 查看日志
|
||||
# 5. 查看日志
|
||||
docker-compose logs -f
|
||||
```
|
||||
|
||||
@@ -129,9 +148,22 @@ docker-compose logs -f
|
||||
|
||||
1. Fork 仓库到自己的 GitHub
|
||||
2. 在 Vercel 上导入项目
|
||||
3. 配置环境变量(至少设置 `DS2API_ADMIN_KEY` 和 `DS2API_CONFIG_JSON`)
|
||||
3. 配置环境变量(最少设置 `DS2API_ADMIN_KEY`;推荐同时设置 `DS2API_CONFIG_JSON`)
|
||||
4. 部署
|
||||
|
||||
建议先在仓库目录复制模板并填写:
|
||||
|
||||
```bash
|
||||
cp config.example.json config.json
|
||||
# 编辑 config.json
|
||||
```
|
||||
|
||||
推荐:先本地把 `config.json` 转成 Base64,再粘贴到 `DS2API_CONFIG_JSON`,避免 JSON 格式错误:
|
||||
|
||||
```bash
|
||||
base64 < config.json | tr -d '\n'
|
||||
```
|
||||
|
||||
> **流式说明**:`/v1/chat/completions` 在 Vercel 上默认走 `api/chat-stream.js`(Node Runtime)以保证实时 SSE。鉴权、账号选择、会话/PoW 准备仍由 Go 内部 prepare 接口完成;流式响应(含 `tools`)在 Node 侧执行与 Go 对齐的输出组装与防泄漏处理。
|
||||
|
||||
详细部署说明请参阅 [部署指南](DEPLOY.md)。
|
||||
|
||||
42
README.en.md
42
README.en.md
@@ -88,6 +88,19 @@ In addition, `/anthropic/v1/models` now includes historical Claude 1.x/2.x/3.x/4
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Universal First Step (all deployment modes)
|
||||
|
||||
Use `config.json` as the single source of truth (recommended):
|
||||
|
||||
```bash
|
||||
cp config.example.json config.json
|
||||
# Edit config.json
|
||||
```
|
||||
|
||||
Recommended per deployment mode:
|
||||
- Local run: read `config.json` directly
|
||||
- Docker / Vercel: generate Base64 from `config.json` and inject as `DS2API_CONFIG_JSON`
|
||||
|
||||
### Option 1: Local Run
|
||||
|
||||
**Prerequisites**: Go 1.24+, Node.js 20+ (only if building WebUI locally)
|
||||
@@ -112,14 +125,20 @@ Default URL: `http://localhost:5001`
|
||||
### Option 2: Docker
|
||||
|
||||
```bash
|
||||
# 1. Configure environment
|
||||
# 1. Prepare env file
|
||||
cp .env.example .env
|
||||
# Edit .env
|
||||
|
||||
# 2. Start
|
||||
# 2. Generate DS2API_CONFIG_JSON from config.json (single-line Base64)
|
||||
DS2API_CONFIG_JSON="$(base64 < config.json | tr -d '\n')"
|
||||
|
||||
# 3. Edit .env and set:
|
||||
# DS2API_ADMIN_KEY=replace-with-a-strong-secret
|
||||
# DS2API_CONFIG_JSON=${DS2API_CONFIG_JSON}
|
||||
|
||||
# 4. Start
|
||||
docker-compose up -d
|
||||
|
||||
# 3. View logs
|
||||
# 5. View logs
|
||||
docker-compose logs -f
|
||||
```
|
||||
|
||||
@@ -129,9 +148,22 @@ Rebuild after updates: `docker-compose up -d --build`
|
||||
|
||||
1. Fork this repo to your GitHub account
|
||||
2. Import the project on Vercel
|
||||
3. Set environment variables (minimum: `DS2API_ADMIN_KEY` and `DS2API_CONFIG_JSON`)
|
||||
3. Set environment variables (minimum: `DS2API_ADMIN_KEY`; recommended to also set `DS2API_CONFIG_JSON`)
|
||||
4. Deploy
|
||||
|
||||
Recommended first step in repo root:
|
||||
|
||||
```bash
|
||||
cp config.example.json config.json
|
||||
# Edit config.json
|
||||
```
|
||||
|
||||
Recommended: convert `config.json` to Base64 locally, then paste into `DS2API_CONFIG_JSON` to avoid JSON formatting mistakes:
|
||||
|
||||
```bash
|
||||
base64 < config.json | tr -d '\n'
|
||||
```
|
||||
|
||||
> **Streaming note**: `/v1/chat/completions` on Vercel is routed to `api/chat-stream.js` (Node Runtime) for real-time SSE. Auth, account selection, and session/PoW preparation are still handled by the Go internal prepare endpoint; streaming output (including `tools`) is assembled on Node with Go-aligned anti-leak handling.
|
||||
|
||||
For detailed deployment instructions, see the [Deployment Guide](DEPLOY.en.md).
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -101,17 +102,29 @@ func (c *Config) UnmarshalJSON(b []byte) error {
|
||||
for k, v := range raw {
|
||||
switch k {
|
||||
case "keys":
|
||||
_ = json.Unmarshal(v, &c.Keys)
|
||||
if err := json.Unmarshal(v, &c.Keys); err != nil {
|
||||
return fmt.Errorf("invalid field %q: %w", k, err)
|
||||
}
|
||||
case "accounts":
|
||||
_ = json.Unmarshal(v, &c.Accounts)
|
||||
if err := json.Unmarshal(v, &c.Accounts); err != nil {
|
||||
return fmt.Errorf("invalid field %q: %w", k, err)
|
||||
}
|
||||
case "claude_mapping":
|
||||
_ = json.Unmarshal(v, &c.ClaudeMapping)
|
||||
if err := json.Unmarshal(v, &c.ClaudeMapping); err != nil {
|
||||
return fmt.Errorf("invalid field %q: %w", k, err)
|
||||
}
|
||||
case "claude_model_mapping":
|
||||
_ = json.Unmarshal(v, &c.ClaudeModelMap)
|
||||
if err := json.Unmarshal(v, &c.ClaudeModelMap); err != nil {
|
||||
return fmt.Errorf("invalid field %q: %w", k, err)
|
||||
}
|
||||
case "_vercel_sync_hash":
|
||||
_ = json.Unmarshal(v, &c.VercelSyncHash)
|
||||
if err := json.Unmarshal(v, &c.VercelSyncHash); err != nil {
|
||||
return fmt.Errorf("invalid field %q: %w", k, err)
|
||||
}
|
||||
case "_vercel_sync_time":
|
||||
_ = json.Unmarshal(v, &c.VercelSyncTime)
|
||||
if err := json.Unmarshal(v, &c.VercelSyncTime); err != nil {
|
||||
return fmt.Errorf("invalid field %q: %w", k, err)
|
||||
}
|
||||
default:
|
||||
var anyVal any
|
||||
if err := json.Unmarshal(v, &anyVal); err == nil {
|
||||
@@ -233,30 +246,94 @@ func loadConfig() (Config, bool, error) {
|
||||
|
||||
content, err := os.ReadFile(ConfigPath())
|
||||
if err != nil {
|
||||
if IsVercel() {
|
||||
// Vercel one-click deploy may start without a writable/present config file.
|
||||
// Keep an in-memory config so users can bootstrap via WebUI then sync env.
|
||||
return Config{}, true, nil
|
||||
}
|
||||
return Config{}, false, err
|
||||
}
|
||||
var cfg Config
|
||||
if err := json.Unmarshal(content, &cfg); err != nil {
|
||||
return Config{}, false, err
|
||||
}
|
||||
if IsVercel() {
|
||||
// Vercel filesystem is ephemeral/read-only for runtime writes; avoid save errors.
|
||||
return cfg, true, nil
|
||||
}
|
||||
return cfg, false, nil
|
||||
}
|
||||
|
||||
func parseConfigString(raw string) (Config, error) {
|
||||
var cfg Config
|
||||
if err := json.Unmarshal([]byte(raw), &cfg); err == nil {
|
||||
return cfg, nil
|
||||
candidates := []string{raw}
|
||||
if normalized := normalizeConfigInput(raw); normalized != raw {
|
||||
candidates = append(candidates, normalized)
|
||||
}
|
||||
decoded, err := base64.StdEncoding.DecodeString(raw)
|
||||
for _, candidate := range candidates {
|
||||
if err := json.Unmarshal([]byte(candidate), &cfg); err == nil {
|
||||
return cfg, nil
|
||||
}
|
||||
}
|
||||
|
||||
base64Input := candidates[len(candidates)-1]
|
||||
decoded, err := decodeConfigBase64(base64Input)
|
||||
if err != nil {
|
||||
return Config{}, err
|
||||
return Config{}, fmt.Errorf("invalid DS2API_CONFIG_JSON: %w", err)
|
||||
}
|
||||
if err := json.Unmarshal(decoded, &cfg); err != nil {
|
||||
return Config{}, err
|
||||
return Config{}, fmt.Errorf("invalid DS2API_CONFIG_JSON decoded JSON: %w", err)
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func normalizeConfigInput(raw string) string {
|
||||
normalized := strings.TrimSpace(raw)
|
||||
if normalized == "" {
|
||||
return normalized
|
||||
}
|
||||
for {
|
||||
changed := false
|
||||
if len(normalized) >= 2 {
|
||||
first := normalized[0]
|
||||
last := normalized[len(normalized)-1]
|
||||
if (first == '"' && last == '"') || (first == '\'' && last == '\'') {
|
||||
normalized = strings.TrimSpace(normalized[1 : len(normalized)-1])
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(strings.ToLower(normalized), "base64:") {
|
||||
normalized = strings.TrimSpace(normalized[len("base64:"):])
|
||||
changed = true
|
||||
}
|
||||
if !changed {
|
||||
break
|
||||
}
|
||||
}
|
||||
return strings.TrimSpace(normalized)
|
||||
}
|
||||
|
||||
func decodeConfigBase64(raw string) ([]byte, error) {
|
||||
encodings := []*base64.Encoding{
|
||||
base64.StdEncoding,
|
||||
base64.RawStdEncoding,
|
||||
base64.URLEncoding,
|
||||
base64.RawURLEncoding,
|
||||
}
|
||||
var lastErr error
|
||||
for _, enc := range encodings {
|
||||
decoded, err := enc.DecodeString(raw)
|
||||
if err == nil {
|
||||
return decoded, nil
|
||||
}
|
||||
lastErr = err
|
||||
}
|
||||
if lastErr != nil {
|
||||
return nil, lastErr
|
||||
}
|
||||
return nil, errors.New("base64 decode failed")
|
||||
}
|
||||
|
||||
func (s *Store) Snapshot() Config {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
@@ -70,3 +71,53 @@ func TestStoreUpdateAccountTokenKeepsOldAndNewIdentifierResolvable(t *testing.T)
|
||||
t.Fatalf("expected find by old identifier alias")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadStoreRejectsInvalidFieldType(t *testing.T) {
|
||||
t.Setenv("DS2API_CONFIG_JSON", `{"keys":"not-array","accounts":[]}`)
|
||||
store := LoadStore()
|
||||
if len(store.Keys()) != 0 || len(store.Accounts()) != 0 {
|
||||
t.Fatalf("expected empty store when config type is invalid")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseConfigStringSupportsQuotedBase64Prefix(t *testing.T) {
|
||||
rawJSON := `{"keys":["k1"],"accounts":[{"email":"u@example.com","password":"p"}]}`
|
||||
b64 := base64.StdEncoding.EncodeToString([]byte(rawJSON))
|
||||
cfg, err := parseConfigString(`"base64:` + b64 + `"`)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected parse error: %v", err)
|
||||
}
|
||||
if len(cfg.Keys) != 1 || cfg.Keys[0] != "k1" {
|
||||
t.Fatalf("unexpected keys: %#v", cfg.Keys)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseConfigStringSupportsRawURLBase64(t *testing.T) {
|
||||
rawJSON := `{"keys":["k-url"],"accounts":[]}`
|
||||
b64 := base64.RawURLEncoding.EncodeToString([]byte(rawJSON))
|
||||
cfg, err := parseConfigString(b64)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected parse error: %v", err)
|
||||
}
|
||||
if len(cfg.Keys) != 1 || cfg.Keys[0] != "k-url" {
|
||||
t.Fatalf("unexpected keys: %#v", cfg.Keys)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadConfigOnVercelWithoutConfigFileFallsBackToMemory(t *testing.T) {
|
||||
t.Setenv("VERCEL", "1")
|
||||
t.Setenv("DS2API_CONFIG_JSON", "")
|
||||
t.Setenv("CONFIG_JSON", "")
|
||||
t.Setenv("DS2API_CONFIG_PATH", "testdata/does-not-exist.json")
|
||||
|
||||
cfg, fromEnv, err := loadConfig()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !fromEnv {
|
||||
t.Fatalf("expected fromEnv=true for vercel fallback")
|
||||
}
|
||||
if len(cfg.Keys) != 0 || len(cfg.Accounts) != 0 {
|
||||
t.Fatalf("expected empty bootstrap config, got keys=%d accounts=%d", len(cfg.Keys), len(cfg.Accounts))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user