feat: client Google Sheets + parseSheetRows (TDD)

Implémente la lecture depuis Google Sheets avec la fonction de parsing en TDD.

- parseSheetRows(): parseur pur, testé avec 7 cas (tests unitaires)
- readSheetProducts(): client async qui utilise l'API v4 de Google Sheets
- Normalisation des handles (minuscules, tirets, trim)
- Gestion des prix français (virgule → point), status inactif → draft
- Valeurs par défaut pour colonnes manquantes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-08 20:44:26 +02:00
parent 1daf9bda72
commit c4010e8c5e
2 changed files with 131 additions and 0 deletions
+62
View File
@@ -0,0 +1,62 @@
import { parseSheetRows } from '@/lib/googleSheets'
describe('parseSheetRows', () => {
it('parse une ligne complète', () => {
const rows = [['REF-1042', 'Carrelage sol', 'Beau carrelage', '29.99', '15', 'actif']]
const result = parseSheetRows(rows)
expect(result).toHaveLength(1)
expect(result[0]).toEqual({
ref: 'REF-1042',
handle: 'ref-1042',
title: 'Carrelage sol',
description: 'Beau carrelage',
price: '29.99',
stock: 15,
status: 'active',
})
})
it('normalise le handle en minuscules avec tirets', () => {
const rows = [['ART 001/B', 'Produit', '', '10.00', '0', 'actif']]
const [p] = parseSheetRows(rows)
expect(p.handle).toBe('art-001-b')
})
it('ignore les lignes sans référence', () => {
const rows = [['', 'Titre vide', '', '0', '0', ''], ['REF-1', 'OK', '', '5.00', '1', 'actif']]
expect(parseSheetRows(rows)).toHaveLength(1)
})
it('gère un prix avec virgule (format français)', () => {
const rows = [['REF-2', 'Test', '', '19,99', '0', 'actif']]
expect(parseSheetRows(rows)[0].price).toBe('19.99')
})
it('marque comme draft si la colonne statut contient "inac"', () => {
const rows = [
['REF-3', 'A', '', '1.00', '0', 'Inactif'],
['REF-4', 'B', '', '1.00', '0', 'inactif'],
['REF-5', 'C', '', '1.00', '0', 'actif'],
['REF-6', 'D', '', '1.00', '0', ''],
]
const results = parseSheetRows(rows)
expect(results[0].status).toBe('draft')
expect(results[1].status).toBe('draft')
expect(results[2].status).toBe('active')
expect(results[3].status).toBe('active')
})
it('utilise la référence comme titre si le titre est vide', () => {
const rows = [['REF-7', '', '', '5.00', '0', 'actif']]
expect(parseSheetRows(rows)[0].title).toBe('REF-7')
})
it('accepte des lignes avec colonnes manquantes', () => {
const rows = [['REF-8']]
const [p] = parseSheetRows(rows)
expect(p.ref).toBe('REF-8')
expect(p.price).toBe('0.00')
expect(p.stock).toBe(0)
expect(p.status).toBe('active')
})
})