Skip to content

Write Tools — QA Test Cases

Source: src/mcp_gee_sweet/tools/write.py

Fixtures: see docs/qa/setup.md. Substitute your {SPREADSHEET_ID} from fixtures.local.md.

Many write tests mutate the fixture spreadsheet. Tests marked ⚠️ destructive should be run last or followed by a fixture reset.


update_cells

TC-W01: Write simple values

Prompt

"Write the values 'Test', 'A', 'B', 'C' into row 7 of the Sales sheet in {SPREADSHEET_ID}, starting at column A"

Checks - Row 7 in Sales now contains: Test, A, B, C - Existing rows 1–6 unchanged - No error field


TC-W02: Write a formula via USER_ENTERED ⚠️ destructive

Prompt

"Write the formula =A2&' '&A3 into cell E2 of the Sales sheet in {SPREADSHEET_ID}"

Checks - E2 shows computed value "Widget Gadget" (formula evaluated, not stored as string) - Confirms USER_ENTERED input mode


TC-W03: Range smaller than data provided

Prompt

"Write these four values — Alpha, Beta, Gamma, Delta — into just cells A8:A9 of the Sales sheet in {SPREADSHEET_ID}"

Checks - Returns an error — the API rejects an oversized array with a 400 "tried writing to row [N]" error - No values are written - 🔍 Product decision: should the tool pre-truncate the data to fit the range instead of letting the API error?


TC-W04: Cache invalidated after write

Prompt (run TC-R16 or TC-R17 first to warm the cache, then run this)

"Update cell A7 in the Sales sheet of {SPREADSHEET_ID} to 'CacheTest', then immediately summarize that spreadsheet"

Checks - Summary reflects the new value in A7 — not stale cached data - Confirms sheet_data_cache.mark_dirty fired after the write


TC-W05: Non-existent sheet name

Prompt

"Write 'Hello' into cell A1 of a sheet called 'NoSuchSheet' in {SPREADSHEET_ID}"

Checks - Returns a clear API error - Does not silently succeed or create the sheet


batch_update_cells

TC-W06: Multiple ranges in one call

Prompt

"In {SPREADSHEET_ID}, update two ranges at once: write 'Batch1' into Sales!A8 and 'Batch2' into Sales!A9"

Checks - Both cells updated in a single operation - A8 = Batch1, A9 = Batch2


TC-W07: Ranges on the same sheet

Prompt

"Update Sales!B8 to 999 and Sales!C8 to 888 in {SPREADSHEET_ID} in one batch call"

Checks - Both cells updated correctly - No conflict from writing multiple ranges to the same sheet


TC-W08: Empty ranges dict

Prompt

"Do a batch cell update on {SPREADSHEET_ID} with an empty set of ranges — no ranges to update"

Checks - Returns success or a clear no-op response — not a server error - 🔍 Product decision: should an empty ranges dict be an error or a no-op?


TC-W09: Cache invalidated after batch write

Prompt

"Batch update Sales!A8 to 'dirty' in {SPREADSHEET_ID}, then immediately summarize the spreadsheet"

Checks - Summary reflects A8 = 'dirty' - Confirms sheet_data_cache.mark_dirty is called for batch_update_cells

TC-W10: Trailing empty-array rows are normalized

Prompt

"In {SPREADSHEET_ID}, batch update Sales!A8:B10 with these values: row 1 is ['hello', 'world'], rows 2 and 3 are empty (no data). Check that updatedRange in the response covers all three rows (A8:B10), not just the first."

Checks - updatedRange in the response is Sales!A8:B10, not Sales!A8:B8 - Rows A9 and A10 are cleared (blank) in the sheet - No duplicate row appended after the write


add_rows

TC-W10: Add row at beginning (no position specified)

Prompt

"Add 1 row at the very beginning of the Sales sheet in {SPREADSHEET_ID}"

Checks - New blank row appears above the current row 1 (headers pushed to row 2) - inheritFromBefore=False (inserting at index 0 — no row above to inherit from)


TC-W11: Add row at explicit position ⚠️ destructive

Prompt

"Add 1 row after row 3 of the Sales sheet in {SPREADSHEET_ID}"

Checks - New blank row at position 4 - Existing rows 4–6 shifted down


TC-W12: start_row=0 — inheritFromBefore=False

Prompt

"Add 1 row at position 0 (the very first row) of the Sales sheet in {SPREADSHEET_ID}"

Checks - Row inserted at the top - inheritFromBefore is False (0 is not > 0) - 🔍 Product decision: is start_row=0 meaningfully different from start_row=None?


TC-W13: start_row=1 — inheritFromBefore=True

Prompt

"Add 1 row at position 1 in the Sales sheet of {SPREADSHEET_ID}"

Checks - Row inserted at index 1 (after the first row) - inheritFromBefore=True — new row inherits formatting from row above


TC-W14: Add multiple rows at once

Prompt

"Add 5 blank rows at the end of the Sales sheet in {SPREADSHEET_ID}"

Checks - 5 new rows appear - Existing data unchanged


TC-W15: Invalid sheet name

Prompt

"Add 1 row to a sheet called 'NoSuchSheet' in {SPREADSHEET_ID}"

Checks - Returns {"error": ...} without calling the Sheets API - Does not throw an unhandled exception


TC-W16: Large count value

Prompt

"Add 1000 rows to the Sales sheet in {SPREADSHEET_ID}"

Checks - Succeeds or returns a clear API limit error - 🔍 Product decision: should the tool cap count to prevent accidental large inserts?


add_columns

TC-W17: Add column at beginning (no position specified)

Prompt

"Add 1 column at the very beginning of the Sales sheet in {SPREADSHEET_ID}"

Checks - New blank column A; existing columns shift right (Product moves to B) - inheritFromBefore=False


TC-W18: start_column=0 — inheritFromBefore=False

Prompt

"Add 1 column at position 0 of the Sales sheet in {SPREADSHEET_ID}"

Checks - Column inserted at the leftmost position - inheritFromBefore is False


TC-W19: start_column=1 — inheritFromBefore=True

Prompt

"Add 1 column at position 1 in the Sales sheet of {SPREADSHEET_ID}"

Checks - Column inserted after column A - inheritFromBefore=True


TC-W20: Add multiple columns

Prompt

"Add 3 columns at position 2 in the Sales sheet of {SPREADSHEET_ID}"

Checks - 3 new blank columns inserted at the correct position - Existing columns shifted right


batch_update (raw passthrough)

TC-W21: Add a sheet via raw request

Prompt

"Use a raw batch update on {SPREADSHEET_ID} to add a new sheet called 'RawAdded'"

Checks - New sheet 'RawAdded' appears in the spreadsheet - Response includes the new sheet's properties


TC-W22: Rename a sheet via raw request

Prompt

"Use a raw batch update to rename the 'Empty' sheet to 'RawRenamed' in {SPREADSHEET_ID}"

Checks - Sheet formerly called 'Empty' is now 'RawRenamed' - list_sheets reflects the new name


TC-W23: Insert dimension via raw request

Prompt

"Use a raw batch update on {SPREADSHEET_ID} to insert 2 rows at index 1 in the Sales sheet"

Checks - 2 new rows appear at index 1 - Existing data shifts down


TC-W24: Delete dimension — structure cache invalidated ⚠️ destructive

Prompt

"Use a raw batch update to delete row 7 from the Sales sheet in {SPREADSHEET_ID}, then list the sheets"

Checks - Row 7 deleted - list_sheets call after reflects current state — not stale cache - Confirms the structure cache bug fix: both cache.mark_dirty and sheet_data_cache.mark_dirty are called


TC-W25: Empty requests list

Prompt

"Send a raw batch update to {SPREADSHEET_ID} with an empty requests list"

Checks - Returns {"error": "requests list cannot be empty"} or similar - Does not call the Sheets API


TC-W26: Non-dict item in requests

Prompt

"Send a raw batch update to {SPREADSHEET_ID} with a requests list containing the string 'notadict'"

Checks - Returns an error field - Does not forward the malformed request to the API


TC-W27: Invalid request structure

Prompt

"Send a raw batch update to {SPREADSHEET_ID} with requests: [{\"unknownKey\": {}}]"

Checks - API error propagates back — not a server crash - Error message is from the Sheets API (unknown request type)


TC-W28: Both caches marked dirty

Prompt

"Use a raw batch update to add a sheet called 'CacheCheck' to {SPREADSHEET_ID}, then immediately list all sheets and summarize the spreadsheet"

Checks - list_sheets includes 'CacheCheck' — structure cache was invalidated - Summary reflects the new sheet — data cache was invalidated - Confirms both cache.mark_dirty and sheet_data_cache.mark_dirty are called


clear_values

TC-W29: Clear a specific range ⚠️ destructive

Prompt

"Clear the values in cells A1:C5 of the Sales sheet in {SPREADSHEET_ID}, leaving formatting intact"

Setup: Confirm A1:C5 has values and formatting before clearing.

Checks - Cells A1:C5 are empty (no values) - Cell formatting (background color, borders, number format) is unchanged - No error field

Result (2026-06-21) ✅ A1:C5 cleared. get_sheet_data confirmed columns A–C rows 1–5 empty; column D (Q3) and row 6 (Totals) untouched. SUM formulas recalculated to 0 for cleared columns.


TC-W30: Clear entire sheet ⚠️ destructive

Prompt

"Clear all values from the Notes sheet in {SPREADSHEET_ID}"

Checks - Sheet is empty (all cells blank) - No error field - Formatting preserved (not a full delete)

Result (2026-06-21) ✅ clear_values called with no range on Notes & Misc. get_sheet_data returned values: []. No error field. clearedRange: "'Notes & Misc'!A1:Z1000".


TC-W31: Clear values — sheet name with spaces

Prompt

"Clear cells B2:D4 from the 'Notes & Misc' sheet in {SPREADSHEET_ID}"

Checks - Range string sent to API is 'Notes & Misc'!B2:D4 (sheet name single-quoted) - Values cleared successfully

Result (2026-06-21) ✅ clearedRange in response was 'Notes & Misc'!B2:D4 — single-quoting applied correctly. No error.


TC-W32: Clear non-existent range — API behaviour

Prompt

"Clear cells Z100:Z200 from the Sales sheet in {SPREADSHEET_ID}"

Checks - API returns a clearedRange with an adjusted or empty range (no error — the API accepts out-of-bounds ranges)

Result (2026-06-21) ✅ Response: {"clearedRange":"Sales!Z100:Z200"} — API accepted the out-of-bounds range and returned it as-is with no error.