# GAS 測試環境設定

這份後端只供第 23–28 章運動器材電商練習使用。請建立全新的測試 Spreadsheet 與測試 Web App deployment；不得連接公司、客戶或正式環境資料。

## 1. 建立測試 Spreadsheet

建立空白 Google Spreadsheet，記下 ID，但不要把 ID 寫進 Git、HTML、JavaScript 或這份文件。

在 Apps Script 專案的「專案設定 → 指令碼屬性」新增：

- 屬性：`TEST_SPREADSHEET_ID`
- 值：測試 Spreadsheet ID

後端只讀這個 Script Property，不會使用 active spreadsheet 或前端傳入的 ID。

## 2. 建立 Apps Script 專案

1. 建立獨立的測試 Apps Script 專案。
2. 將 `apps-script/Code.gs` 貼入 `Code.gs`。
3. 將 `apps-script/appsscript.json` 貼入 manifest。
4. 在編輯器手動執行一次 `setupTestSheets()` 並完成授權。

函式只會建立缺少的測試工作表與第一列表頭，不會清除既有資料。若已存在的表頭錯誤，會停止並回報 `SCHEMA_ERROR`。

## 3. 三張表的固定 schema

### Products

| productId | name | image | price | salePrice | active |
| --- | --- | --- | ---: | ---: | --- |
| EQ-MAT-02 | 止滑訓練瑜珈墊 | assets/product-mat.svg | 1280 | 980 | TRUE |
| EQ-BIKE-05 | 靜音磁控健身車 | assets/product-bike.svg | 12800 | 10900 | TRUE |
| EQ-INACTIVE-99 | 停售測試商品 | assets/product-mat.svg | 500 |  | FALSE |

- `productId` 必須唯一。
- `price` 是 1–1,000,000 的整數。
- `salePrice` 可空白；有值時必須是正整數且低於 `price`。
- 只有 `active` 為 TRUE／1／yes／是的商品會由 `listProducts` 公開。
- `image` 只填寫本專案已有的 `assets/product-*.svg` 相對路徑，不使用外部或不存在的圖片網址。

### Orders

`orderNo, submissionId, timestamp, buyer, gender, phone, address, email, subtotal, shipping, total, status, itemsJson`

### Contacts

`contactNo, submissionId, timestamp, name, gender, phone, email, message, status`

表頭文字與順序必須完全一致。Orders 與 Contacts 不需要預填測試資料。

## 4. 測試部署

建立 Web App 的測試 deployment。執行身分與存取範圍只開到完成本課測試所需的最小權限；不要重用正式 deployment。將測試網址只填入本機測試設定，不要提交真實 endpoint。

所有 POST 都使用 `application/x-www-form-urlencoded`：

- `action=createOrder` 或 `action=createContact`
- `payload=<JSON 字串>`

後端只從 `e.parameter` 讀取這兩個欄位，response 固定為 `{ ok, data?, message, error? }`。

## 5. 驗證清單

以下命令中的 `<TEST_WEB_APP_URL>` 必須替換為測試 deployment URL；`-L` 用來跟隨 ContentService redirect。

### 只公開 active 商品

~~~bash
curl -L '<TEST_WEB_APP_URL>?action=listProducts'
~~~

確認回應不含 `EQ-INACTIVE-99`，也不含任何 Sheet ID、內部狀態或其他工作表資料。

### 建立訂單

~~~bash
curl -L -X POST '<TEST_WEB_APP_URL>' \
  --data-urlencode 'action=createOrder' \
  --data-urlencode 'payload={"submissionId":"order-test-0001","buyer":"測試學員","gender":"不透露","phone":"0912-345-678","address":"台北市測試區測試路 1 號","email":"student@example.com","items":[{"productId":"EQ-MAT-02","qty":2}],"subtotal":1,"shipping":0,"total":1,"status":"已付款"}'
~~~

確認：

1. 前端傳入的 subtotal、shipping、total、status 都被忽略。
2. 後端從 Products 重新取得商品與價格。
3. 小計由後端計算；滿 NT$2,000 免運，否則運費 NT$120。
4. 初始狀態固定為「待確認」。
5. Orders 只新增一列，itemsJson 是伺服器商品快照。

使用完全相同的 `submissionId` 重送兩次，兩次應回同一個 orderNo，第二次 `duplicate=true`，Orders 仍只有一列。再用兩個終端機同時送出相同請求，確認 LockService 下仍只寫入一次。

將商品改成 `EQ-INACTIVE-99`、不存在的 ID、qty=0、qty=1.5 或 qty=21，逐項確認後端拒絕；不得只看前端按鈕是否 disabled。

### 建立聯絡訊息

~~~bash
curl -L -X POST '<TEST_WEB_APP_URL>' \
  --data-urlencode 'action=createContact' \
  --data-urlencode 'payload={"submissionId":"contact-test-0001","name":"測試學員","gender":"不透露","phone":"0912-345-678","email":"student@example.com","message":"這是一筆測試聯絡訊息","website":""}'
~~~

確認初始狀態固定為「未處理」。相同 submissionId 重送時 Contacts 仍只有一列；將 website 填入任意文字時應回 `HONEYPOT` 且不得寫入。

## 6. 安全與範圍邊界

- 不信任前端金額、狀態、商品名稱、商品圖片或商品價格。
- 不在前端或 Git 內存放 Spreadsheet ID、deployment URL 或任何 secret。
- 僅使用測試 Sheet、假姓名、假電話、假地址與測試信箱。
- 本練習沒有付款、會員登入、庫存扣減、退款或正式訂單流程。
- 完成驗證後可撤銷測試 deployment；不要把測試 deployment 改成正式服務。
