Coffee journaling on ATProto (alpha) alpha.arabica.social
coffee
17
fork

Configure Feed

Select the types of activity you want to include in your feed.

test: convert records_test.go to shutter snapshots

+512 -446
+2
go.mod
··· 57 57 github.com/ipfs/go-metrics-interface v0.0.1 // indirect 58 58 github.com/jbenet/goprocess v0.1.4 // indirect 59 59 github.com/klauspost/cpuid/v2 v2.2.7 // indirect 60 + github.com/kortschak/utter v1.7.0 // indirect 60 61 github.com/mattn/go-colorable v0.1.13 // indirect 61 62 github.com/mattn/go-isatty v0.0.20 // indirect 62 63 github.com/minio/sha256-simd v1.0.1 // indirect ··· 74 75 github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f // indirect 75 76 github.com/prometheus/common v0.66.1 // indirect 76 77 github.com/prometheus/procfs v0.16.1 // indirect 78 + github.com/ptdewey/shutter v0.2.1 // indirect 77 79 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect 78 80 github.com/spaolacci/murmur3 v1.1.0 // indirect 79 81 github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e // indirect
+4
go.sum
··· 102 102 github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= 103 103 github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= 104 104 github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 105 + github.com/kortschak/utter v1.7.0 h1:6NKMynvGUyqfeMTawfah4zyInlrgwzjkDAHrT+skx/w= 106 + github.com/kortschak/utter v1.7.0/go.mod h1:vSmSjbyrlKjjsL71193LmzBOKgwePk9DH6uFaWHIInc= 105 107 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 106 108 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 107 109 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= ··· 154 156 github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= 155 157 github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= 156 158 github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= 159 + github.com/ptdewey/shutter v0.2.1 h1:Xly9ZsgOm+SRDf3pyMWLcJRJWpQNkyh777zW8T9QpIo= 160 + github.com/ptdewey/shutter v0.2.1/go.mod h1:teeIXF4LdgsE9E4kjHk9nGzDxl2cjdbVb1qbdzAHSR4= 157 161 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= 158 162 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= 159 163 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+17
internal/atproto/__snapshots__/beantorecord/full_bean.snap
··· 1 + --- 2 + title: BeanToRecord/full bean 3 + test_name: TestBeanToRecord/full_bean 4 + file_name: records_test.go 5 + version: 0.1.0 6 + --- 7 + map[string]interface{}{ 8 + "$type": "social.arabica.alpha.bean", 9 + "closed": false, 10 + "createdAt": "2025-01-10T12:00:00Z", 11 + "description": "Fruity and floral notes", 12 + "name": "Ethiopian Yirgacheffe", 13 + "origin": "Ethiopia", 14 + "process": "Washed", 15 + "roastLevel": "Light", 16 + "roasterRef": "at://did:plc:test/social.arabica.alpha.roaster/roaster123", 17 + }
+12
internal/atproto/__snapshots__/beantorecord/no_roaster.snap
··· 1 + --- 2 + title: BeanToRecord/no roaster 3 + test_name: TestBeanToRecord/bean_without_roaster 4 + file_name: records_test.go 5 + version: 0.1.0 6 + --- 7 + map[string]interface{}{ 8 + "$type": "social.arabica.alpha.bean", 9 + "closed": false, 10 + "createdAt": "2025-01-10T12:00:00Z", 11 + "name": "Generic Coffee", 12 + }
+12
internal/atproto/__snapshots__/brewertorecord/full_brewer.snap
··· 1 + --- 2 + title: BrewerToRecord/full brewer 3 + test_name: TestBrewerToRecord/full_brewer 4 + file_name: records_test.go 5 + version: 0.1.0 6 + --- 7 + map[string]interface{}{ 8 + "$type": "social.arabica.alpha.brewer", 9 + "createdAt": "2025-01-10T12:00:00Z", 10 + "description": "Pour-over dripper", 11 + "name": "Hario V60", 12 + }
+18
internal/atproto/__snapshots__/brewtorecord/espresso_params.snap
··· 1 + --- 2 + title: BrewToRecord/espresso params 3 + test_name: TestBrewRoundTrip_EspressoParams 4 + file_name: records_test.go 5 + version: 0.1.0 6 + --- 7 + map[string]interface{}{ 8 + "$type": "social.arabica.alpha.brew", 9 + "beanRef": "at://did:plc:test/social.arabica.alpha.bean/abc123", 10 + "createdAt": "2025-01-10T12:00:00Z", 11 + "espressoParams": map[string]interface{}{ 12 + "preInfusionSeconds": 5, 13 + "pressure": 90, 14 + "yieldWeight": 360, 15 + }, 16 + "rating": 8, 17 + "temperature": 935, 18 + }
+30
internal/atproto/__snapshots__/brewtorecord/full_brew.snap
··· 1 + --- 2 + title: BrewToRecord/full brew 3 + test_name: TestBrewToRecord/full_brew_with_all_fields 4 + file_name: records_test.go 5 + version: 0.1.0 6 + --- 7 + map[string]interface{}{ 8 + "$type": "social.arabica.alpha.brew", 9 + "beanRef": "at://did:plc:test/social.arabica.alpha.bean/bean123", 10 + "brewerRef": "at://did:plc:test/social.arabica.alpha.brewer/brewer123", 11 + "createdAt": "2025-01-10T12:00:00Z", 12 + "grindSize": "Medium", 13 + "grinderRef": "at://did:plc:test/social.arabica.alpha.grinder/grinder123", 14 + "method": "V60", 15 + "pours": []map[string]interface{}{ 16 + { 17 + "timeSeconds": 30, 18 + "waterAmount": 50, 19 + }, 20 + { 21 + "timeSeconds": 60, 22 + "waterAmount": 100, 23 + }, 24 + }, 25 + "rating": 8, 26 + "tastingNotes": "Fruity and bright", 27 + "temperature": 935, 28 + "timeSeconds": 180, 29 + "waterAmount": 300, 30 + }
+11
internal/atproto/__snapshots__/brewtorecord/minimal_brew.snap
··· 1 + --- 2 + title: BrewToRecord/minimal brew 3 + test_name: TestBrewToRecord/minimal_brew 4 + file_name: records_test.go 5 + version: 0.1.0 6 + --- 7 + map[string]interface{}{ 8 + "$type": "social.arabica.alpha.brew", 9 + "beanRef": "at://did:plc:test/social.arabica.alpha.bean/bean123", 10 + "createdAt": "2025-01-10T12:00:00Z", 11 + }
+18
internal/atproto/__snapshots__/brewtorecord/pourover_params.snap
··· 1 + --- 2 + title: BrewToRecord/pourover params 3 + test_name: TestBrewRoundTrip_PouroverParams 4 + file_name: records_test.go 5 + version: 0.1.0 6 + --- 7 + map[string]interface{}{ 8 + "$type": "social.arabica.alpha.brew", 9 + "beanRef": "at://did:plc:test/social.arabica.alpha.bean/abc123", 10 + "createdAt": "2025-01-10T12:00:00Z", 11 + "pouroverParams": map[string]interface{}{ 12 + "bloomSeconds": 45, 13 + "bloomWater": 50, 14 + "bypassWater": 100, 15 + "drawdownSeconds": 30, 16 + "filter": "paper", 17 + }, 18 + }
+14
internal/atproto/__snapshots__/grindertorecord/full_grinder.snap
··· 1 + --- 2 + title: GrinderToRecord/full grinder 3 + test_name: TestGrinderToRecord/full_grinder 4 + file_name: records_test.go 5 + version: 0.1.0 6 + --- 7 + map[string]interface{}{ 8 + "$type": "social.arabica.alpha.grinder", 9 + "burrType": "Conical", 10 + "createdAt": "2025-01-10T12:00:00Z", 11 + "grinderType": "Hand", 12 + "name": "Comandante C40", 13 + "notes": "Great for travel", 14 + }
+25
internal/atproto/__snapshots__/recordtobean/full_record.snap
··· 1 + --- 2 + title: RecordToBean/full record 3 + test_name: TestRecordToBean/full_record 4 + file_name: records_test.go 5 + version: 0.1.0 6 + --- 7 + &models.Bean{ 8 + RKey: "bean123", 9 + Name: "Ethiopian Yirgacheffe", 10 + Origin: "Ethiopia", 11 + Variety: "", 12 + RoastLevel: "Light", 13 + Process: "Washed", 14 + Description: "Fruity notes", 15 + RoasterRKey: "", 16 + Rating: (*int)(nil), 17 + Closed: false, 18 + SourceRef: "", 19 + CreatedAt: time.Time{ 20 + wall: 0x0, 21 + ext: 63872107200, 22 + loc: (*time.Location)(nil), 23 + }, 24 + Roaster: (*models.Roaster)(nil), 25 + }
+37
internal/atproto/__snapshots__/recordtobrew/espresso_params.snap
··· 1 + --- 2 + title: RecordToBrew/espresso params 3 + test_name: TestBrewRoundTrip_EspressoParams 4 + file_name: records_test.go 5 + version: 0.1.0 6 + --- 7 + &models.Brew{ 8 + RKey: "tid123", 9 + BeanRKey: "", 10 + RecipeRKey: "", 11 + Method: "", 12 + Temperature: 0.0, 13 + WaterAmount: 0, 14 + CoffeeAmount: 0, 15 + TimeSeconds: 0, 16 + GrindSize: "", 17 + GrinderRKey: "", 18 + BrewerRKey: "", 19 + TastingNotes: "", 20 + Rating: 0, 21 + CreatedAt: time.Time{ 22 + wall: 0x0, 23 + ext: 63872107200, 24 + loc: (*time.Location)(nil), 25 + }, 26 + EspressoParams: &models.EspressoParams{ 27 + YieldWeight: 36.0, 28 + Pressure: 9.0, 29 + PreInfusionSeconds: 5, 30 + }, 31 + PouroverParams: (*models.PouroverParams)(nil), 32 + Bean: (*models.Bean)(nil), 33 + RecipeObj: (*models.Recipe)(nil), 34 + GrinderObj: (*models.Grinder)(nil), 35 + BrewerObj: (*models.Brewer)(nil), 36 + Pours: []*models.Pour(nil), 37 + }
+54
internal/atproto/__snapshots__/recordtobrew/full_record.snap
··· 1 + --- 2 + title: RecordToBrew/full record 3 + test_name: TestRecordToBrew/full_record 4 + file_name: records_test.go 5 + version: 0.1.0 6 + --- 7 + &models.Brew{ 8 + RKey: "brew123", 9 + BeanRKey: "", 10 + RecipeRKey: "", 11 + Method: "V60", 12 + Temperature: 93.5, 13 + WaterAmount: 300, 14 + CoffeeAmount: 0, 15 + TimeSeconds: 180, 16 + GrindSize: "Medium", 17 + GrinderRKey: "", 18 + BrewerRKey: "", 19 + TastingNotes: "Fruity", 20 + Rating: 8, 21 + CreatedAt: time.Time{ 22 + wall: 0x0, 23 + ext: 63872107200, 24 + loc: (*time.Location)(nil), 25 + }, 26 + EspressoParams: (*models.EspressoParams)(nil), 27 + PouroverParams: (*models.PouroverParams)(nil), 28 + Bean: (*models.Bean)(nil), 29 + RecipeObj: (*models.Recipe)(nil), 30 + GrinderObj: (*models.Grinder)(nil), 31 + BrewerObj: (*models.Brewer)(nil), 32 + Pours: []*models.Pour{ 33 + &models.Pour{ 34 + PourNumber: 1, 35 + WaterAmount: 50, 36 + TimeSeconds: 30, 37 + CreatedAt: time.Time{ 38 + wall: 0x0, 39 + ext: 0, 40 + loc: (*time.Location)(nil), 41 + }, 42 + }, 43 + &models.Pour{ 44 + PourNumber: 2, 45 + WaterAmount: 100, 46 + TimeSeconds: 60, 47 + CreatedAt: time.Time{ 48 + wall: 0x0, 49 + ext: 0, 50 + loc: (*time.Location)(nil), 51 + }, 52 + }, 53 + }, 54 + }
+39
internal/atproto/__snapshots__/recordtobrew/pourover_params.snap
··· 1 + --- 2 + title: RecordToBrew/pourover params 3 + test_name: TestBrewRoundTrip_PouroverParams 4 + file_name: records_test.go 5 + version: 0.1.0 6 + --- 7 + &models.Brew{ 8 + RKey: "tid123", 9 + BeanRKey: "", 10 + RecipeRKey: "", 11 + Method: "", 12 + Temperature: 0.0, 13 + WaterAmount: 0, 14 + CoffeeAmount: 0, 15 + TimeSeconds: 0, 16 + GrindSize: "", 17 + GrinderRKey: "", 18 + BrewerRKey: "", 19 + TastingNotes: "", 20 + Rating: 0, 21 + CreatedAt: time.Time{ 22 + wall: 0x0, 23 + ext: 63872107200, 24 + loc: (*time.Location)(nil), 25 + }, 26 + EspressoParams: (*models.EspressoParams)(nil), 27 + PouroverParams: &models.PouroverParams{ 28 + BloomWater: 50, 29 + BloomSeconds: 45, 30 + DrawdownSeconds: 30, 31 + BypassWater: 100, 32 + Filter: "paper", 33 + }, 34 + Bean: (*models.Bean)(nil), 35 + RecipeObj: (*models.Recipe)(nil), 36 + GrinderObj: (*models.Grinder)(nil), 37 + BrewerObj: (*models.Brewer)(nil), 38 + Pours: []*models.Pour(nil), 39 + }
+18
internal/atproto/__snapshots__/recordtobrewer/full_record.snap
··· 1 + --- 2 + title: RecordToBrewer/full record 3 + test_name: TestRecordToBrewer/full_record 4 + file_name: records_test.go 5 + version: 0.1.0 6 + --- 7 + &models.Brewer{ 8 + RKey: "brewer123", 9 + Name: "Hario V60", 10 + BrewerType: "", 11 + Description: "Pour-over dripper", 12 + SourceRef: "", 13 + CreatedAt: time.Time{ 14 + wall: 0x0, 15 + ext: 63872107200, 16 + loc: (*time.Location)(nil), 17 + }, 18 + }
+19
internal/atproto/__snapshots__/recordtogrinder/full_record.snap
··· 1 + --- 2 + title: RecordToGrinder/full record 3 + test_name: TestRecordToGrinder/full_record 4 + file_name: records_test.go 5 + version: 0.1.0 6 + --- 7 + &models.Grinder{ 8 + RKey: "grinder123", 9 + Name: "Comandante C40", 10 + GrinderType: "Hand", 11 + BurrType: "Conical", 12 + Notes: "Great for travel", 13 + SourceRef: "", 14 + CreatedAt: time.Time{ 15 + wall: 0x0, 16 + ext: 63872107200, 17 + loc: (*time.Location)(nil), 18 + }, 19 + }
+18
internal/atproto/__snapshots__/recordtoroaster/full_record.snap
··· 1 + --- 2 + title: RecordToRoaster/full record 3 + test_name: TestRecordToRoaster/full_record 4 + file_name: records_test.go 5 + version: 0.1.0 6 + --- 7 + &models.Roaster{ 8 + RKey: "roaster123", 9 + Name: "Counter Culture", 10 + Location: "Durham, NC", 11 + Website: "https://counterculturecoffee.com", 12 + SourceRef: "", 13 + CreatedAt: time.Time{ 14 + wall: 0x0, 15 + ext: 63872107200, 16 + loc: (*time.Location)(nil), 17 + }, 18 + }
+13
internal/atproto/__snapshots__/roastertorecord/full_roaster.snap
··· 1 + --- 2 + title: RoasterToRecord/full roaster 3 + test_name: TestRoasterToRecord/full_roaster 4 + file_name: records_test.go 5 + version: 0.1.0 6 + --- 7 + map[string]interface{}{ 8 + "$type": "social.arabica.alpha.roaster", 9 + "createdAt": "2025-01-10T12:00:00Z", 10 + "location": "Durham, NC", 11 + "name": "Counter Culture", 12 + "website": "https://counterculturecoffee.com", 13 + }
+25
internal/atproto/__snapshots__/roundtrip/bean.snap
··· 1 + --- 2 + title: RoundTrip/bean 3 + test_name: TestRoundTrip/bean_round_trip 4 + file_name: records_test.go 5 + version: 0.1.0 6 + --- 7 + &models.Bean{ 8 + RKey: "bean123", 9 + Name: "Ethiopian Yirgacheffe", 10 + Origin: "Ethiopia", 11 + Variety: "", 12 + RoastLevel: "Light", 13 + Process: "Washed", 14 + Description: "Fruity notes", 15 + RoasterRKey: "", 16 + Rating: (*int)(nil), 17 + Closed: false, 18 + SourceRef: "", 19 + CreatedAt: time.Time{ 20 + wall: 0x0, 21 + ext: 63872107200, 22 + loc: (*time.Location)(nil), 23 + }, 24 + Roaster: (*models.Roaster)(nil), 25 + }
+18
internal/atproto/__snapshots__/roundtrip/brewer.snap
··· 1 + --- 2 + title: RoundTrip/brewer 3 + test_name: TestRoundTrip/brewer_round_trip 4 + file_name: records_test.go 5 + version: 0.1.0 6 + --- 7 + &models.Brewer{ 8 + RKey: "brewer123", 9 + Name: "Hario V60", 10 + BrewerType: "Pour-Over", 11 + Description: "Pour-over dripper", 12 + SourceRef: "", 13 + CreatedAt: time.Time{ 14 + wall: 0x0, 15 + ext: 63872107200, 16 + loc: (*time.Location)(nil), 17 + }, 18 + }
+19
internal/atproto/__snapshots__/roundtrip/grinder.snap
··· 1 + --- 2 + title: RoundTrip/grinder 3 + test_name: TestRoundTrip/grinder_round_trip 4 + file_name: records_test.go 5 + version: 0.1.0 6 + --- 7 + &models.Grinder{ 8 + RKey: "grinder123", 9 + Name: "Comandante C40", 10 + GrinderType: "Hand", 11 + BurrType: "Conical", 12 + Notes: "Great for travel", 13 + SourceRef: "", 14 + CreatedAt: time.Time{ 15 + wall: 0x0, 16 + ext: 63872107200, 17 + loc: (*time.Location)(nil), 18 + }, 19 + }
+18
internal/atproto/__snapshots__/roundtrip/roaster.snap
··· 1 + --- 2 + title: RoundTrip/roaster 3 + test_name: TestRoundTrip/roaster_round_trip 4 + file_name: records_test.go 5 + version: 0.1.0 6 + --- 7 + &models.Roaster{ 8 + RKey: "roaster123", 9 + Name: "Counter Culture", 10 + Location: "Durham, NC", 11 + Website: "https://counterculturecoffee.com", 12 + SourceRef: "", 13 + CreatedAt: time.Time{ 14 + wall: 0x0, 15 + ext: 63872107200, 16 + loc: (*time.Location)(nil), 17 + }, 18 + }
+70 -445
internal/atproto/records_test.go
··· 6 6 7 7 "arabica/internal/models" 8 8 9 + "github.com/ptdewey/shutter" 9 10 "github.com/stretchr/testify/assert" 11 + "github.com/stretchr/testify/require" 10 12 ) 11 13 12 14 func TestBrewToRecord(t *testing.T) { ··· 33 35 brewerURI := "at://did:plc:test/social.arabica.alpha.brewer/brewer123" 34 36 35 37 record, err := BrewToRecord(brew, beanURI, grinderURI, brewerURI, "") 36 - if err != nil { 37 - t.Fatalf("BrewToRecord() error = %v", err) 38 - } 39 - 40 - // Check required fields 41 - if record["$type"] != NSIDBrew { 42 - t.Errorf("$type = %v, want %v", record["$type"], NSIDBrew) 43 - } 44 - if record["beanRef"] != beanURI { 45 - t.Errorf("beanRef = %v, want %v", record["beanRef"], beanURI) 46 - } 47 - if record["createdAt"] != "2025-01-10T12:00:00Z" { 48 - t.Errorf("createdAt = %v, want %v", record["createdAt"], "2025-01-10T12:00:00Z") 49 - } 50 - 51 - // Check optional fields 52 - if record["method"] != "V60" { 53 - t.Errorf("method = %v, want %v", record["method"], "V60") 54 - } 55 - // Temperature should be converted to tenths (93.5 -> 935) 56 - if record["temperature"] != 935 { 57 - t.Errorf("temperature = %v, want %v", record["temperature"], 935) 58 - } 59 - if record["waterAmount"] != 300 { 60 - t.Errorf("waterAmount = %v, want %v", record["waterAmount"], 300) 61 - } 62 - if record["timeSeconds"] != 180 { 63 - t.Errorf("timeSeconds = %v, want %v", record["timeSeconds"], 180) 64 - } 65 - if record["grindSize"] != "Medium" { 66 - t.Errorf("grindSize = %v, want %v", record["grindSize"], "Medium") 67 - } 68 - if record["grinderRef"] != grinderURI { 69 - t.Errorf("grinderRef = %v, want %v", record["grinderRef"], grinderURI) 70 - } 71 - if record["brewerRef"] != brewerURI { 72 - t.Errorf("brewerRef = %v, want %v", record["brewerRef"], brewerURI) 73 - } 74 - if record["tastingNotes"] != "Fruity and bright" { 75 - t.Errorf("tastingNotes = %v, want %v", record["tastingNotes"], "Fruity and bright") 76 - } 77 - if record["rating"] != 8 { 78 - t.Errorf("rating = %v, want %v", record["rating"], 8) 79 - } 80 - 81 - // Check pours 82 - pours, ok := record["pours"].([]map[string]any) 83 - if !ok { 84 - t.Fatalf("pours is not []map[string]interface{}") 85 - } 86 - if len(pours) != 2 { 87 - t.Errorf("len(pours) = %v, want %v", len(pours), 2) 88 - } 89 - if pours[0]["waterAmount"] != 50 { 90 - t.Errorf("pours[0].waterAmount = %v, want %v", pours[0]["waterAmount"], 50) 91 - } 38 + require.NoError(t, err) 39 + shutter.Snap(t, "BrewToRecord/full brew", record) 92 40 }) 93 41 94 42 t.Run("minimal brew", func(t *testing.T) { ··· 96 44 CreatedAt: createdAt, 97 45 } 98 46 99 - beanURI := "at://did:plc:test/social.arabica.alpha.bean/bean123" 100 - 101 - record, err := BrewToRecord(brew, beanURI, "", "", "") 102 - if err != nil { 103 - t.Fatalf("BrewToRecord() error = %v", err) 104 - } 105 - 106 - // Optional fields should be omitted 107 - if _, ok := record["method"]; ok { 108 - t.Error("method should be omitted when empty") 109 - } 110 - if _, ok := record["temperature"]; ok { 111 - t.Error("temperature should be omitted when zero") 112 - } 113 - if _, ok := record["grinderRef"]; ok { 114 - t.Error("grinderRef should be omitted when empty") 115 - } 116 - if _, ok := record["brewerRef"]; ok { 117 - t.Error("brewerRef should be omitted when empty") 118 - } 119 - if _, ok := record["pours"]; ok { 120 - t.Error("pours should be omitted when empty") 121 - } 47 + record, err := BrewToRecord(brew, "at://did:plc:test/social.arabica.alpha.bean/bean123", "", "", "") 48 + require.NoError(t, err) 49 + shutter.Snap(t, "BrewToRecord/minimal brew", record) 122 50 }) 123 51 124 52 t.Run("error without beanURI", func(t *testing.T) { 125 - brew := &models.Brew{ 126 - CreatedAt: createdAt, 127 - } 128 - 53 + brew := &models.Brew{CreatedAt: createdAt} 129 54 _, err := BrewToRecord(brew, "", "", "", "") 130 - if err == nil { 131 - t.Error("BrewToRecord() should error without beanURI") 132 - } 55 + assert.Error(t, err) 133 56 }) 134 57 } 135 58 ··· 140 63 "beanRef": "at://did:plc:test/social.arabica.alpha.bean/bean123", 141 64 "createdAt": "2025-01-10T12:00:00Z", 142 65 "method": "V60", 143 - "temperature": float64(935), // tenths 66 + "temperature": float64(935), 144 67 "waterAmount": float64(300), 145 68 "timeSeconds": float64(180), 146 69 "grindSize": "Medium", ··· 154 77 }, 155 78 } 156 79 157 - atURI := "at://did:plc:test/social.arabica.alpha.brew/brew123" 158 - brew, err := RecordToBrew(record, atURI) 159 - if err != nil { 160 - t.Fatalf("RecordToBrew() error = %v", err) 161 - } 162 - 163 - if brew.RKey != "brew123" { 164 - t.Errorf("RKey = %v, want %v", brew.RKey, "brew123") 165 - } 166 - if brew.Method != "V60" { 167 - t.Errorf("Method = %v, want %v", brew.Method, "V60") 168 - } 169 - // Temperature should be converted from tenths (935 -> 93.5) 170 - if brew.Temperature != 93.5 { 171 - t.Errorf("Temperature = %v, want %v", brew.Temperature, 93.5) 172 - } 173 - if brew.WaterAmount != 300 { 174 - t.Errorf("WaterAmount = %v, want %v", brew.WaterAmount, 300) 175 - } 176 - if brew.TimeSeconds != 180 { 177 - t.Errorf("TimeSeconds = %v, want %v", brew.TimeSeconds, 180) 178 - } 179 - if brew.GrindSize != "Medium" { 180 - t.Errorf("GrindSize = %v, want %v", brew.GrindSize, "Medium") 181 - } 182 - if brew.TastingNotes != "Fruity" { 183 - t.Errorf("TastingNotes = %v, want %v", brew.TastingNotes, "Fruity") 184 - } 185 - if brew.Rating != 8 { 186 - t.Errorf("Rating = %v, want %v", brew.Rating, 8) 187 - } 188 - 189 - if len(brew.Pours) != 2 { 190 - t.Fatalf("len(Pours) = %v, want %v", len(brew.Pours), 2) 191 - } 192 - if brew.Pours[0].WaterAmount != 50 { 193 - t.Errorf("Pours[0].WaterAmount = %v, want %v", brew.Pours[0].WaterAmount, 50) 194 - } 195 - if brew.Pours[0].PourNumber != 1 { 196 - t.Errorf("Pours[0].PourNumber = %v, want %v", brew.Pours[0].PourNumber, 1) 197 - } 80 + brew, err := RecordToBrew(record, "at://did:plc:test/social.arabica.alpha.brew/brew123") 81 + require.NoError(t, err) 82 + shutter.Snap(t, "RecordToBrew/full record", brew) 198 83 }) 199 84 200 85 t.Run("error without beanRef", func(t *testing.T) { ··· 202 87 "$type": NSIDBrew, 203 88 "createdAt": "2025-01-10T12:00:00Z", 204 89 } 205 - 206 90 _, err := RecordToBrew(record, "at://did:plc:test/social.arabica.alpha.brew/brew123") 207 - if err == nil { 208 - t.Error("RecordToBrew() should error without beanRef") 209 - } 91 + assert.Error(t, err) 210 92 }) 211 93 212 94 t.Run("error without createdAt", func(t *testing.T) { ··· 214 96 "$type": NSIDBrew, 215 97 "beanRef": "at://did:plc:test/social.arabica.alpha.bean/bean123", 216 98 } 217 - 218 99 _, err := RecordToBrew(record, "at://did:plc:test/social.arabica.alpha.brew/brew123") 219 - if err == nil { 220 - t.Error("RecordToBrew() should error without createdAt") 221 - } 100 + assert.Error(t, err) 222 101 }) 223 102 224 103 t.Run("error with invalid AT-URI", func(t *testing.T) { ··· 227 106 "beanRef": "at://did:plc:test/social.arabica.alpha.bean/bean123", 228 107 "createdAt": "2025-01-10T12:00:00Z", 229 108 } 230 - 231 109 _, err := RecordToBrew(record, "invalid-uri") 232 - if err == nil { 233 - t.Error("RecordToBrew() should error with invalid AT-URI") 234 - } 110 + assert.Error(t, err) 235 111 }) 236 112 } 237 113 ··· 247 123 Description: "Fruity and floral notes", 248 124 CreatedAt: createdAt, 249 125 } 250 - 251 - roasterURI := "at://did:plc:test/social.arabica.alpha.roaster/roaster123" 252 - 253 - record, err := BeanToRecord(bean, roasterURI) 254 - if err != nil { 255 - t.Fatalf("BeanToRecord() error = %v", err) 256 - } 257 - 258 - if record["$type"] != NSIDBean { 259 - t.Errorf("$type = %v, want %v", record["$type"], NSIDBean) 260 - } 261 - if record["name"] != "Ethiopian Yirgacheffe" { 262 - t.Errorf("name = %v, want %v", record["name"], "Ethiopian Yirgacheffe") 263 - } 264 - if record["origin"] != "Ethiopia" { 265 - t.Errorf("origin = %v, want %v", record["origin"], "Ethiopia") 266 - } 267 - if record["roastLevel"] != "Light" { 268 - t.Errorf("roastLevel = %v, want %v", record["roastLevel"], "Light") 269 - } 270 - if record["process"] != "Washed" { 271 - t.Errorf("process = %v, want %v", record["process"], "Washed") 272 - } 273 - if record["description"] != "Fruity and floral notes" { 274 - t.Errorf("description = %v, want %v", record["description"], "Fruity and floral notes") 275 - } 276 - if record["roasterRef"] != roasterURI { 277 - t.Errorf("roasterRef = %v, want %v", record["roasterRef"], roasterURI) 278 - } 126 + record, err := BeanToRecord(bean, "at://did:plc:test/social.arabica.alpha.roaster/roaster123") 127 + require.NoError(t, err) 128 + shutter.Snap(t, "BeanToRecord/full bean", record) 279 129 }) 280 130 281 131 t.Run("bean without roaster", func(t *testing.T) { ··· 283 133 Name: "Generic Coffee", 284 134 CreatedAt: createdAt, 285 135 } 286 - 287 136 record, err := BeanToRecord(bean, "") 288 - if err != nil { 289 - t.Fatalf("BeanToRecord() error = %v", err) 290 - } 291 - 292 - if _, ok := record["roasterRef"]; ok { 293 - t.Error("roasterRef should be omitted when empty") 294 - } 137 + require.NoError(t, err) 138 + shutter.Snap(t, "BeanToRecord/no roaster", record) 295 139 }) 296 140 } 297 141 ··· 306 150 "description": "Fruity notes", 307 151 "createdAt": "2025-01-10T12:00:00Z", 308 152 } 309 - 310 - atURI := "at://did:plc:test/social.arabica.alpha.bean/bean123" 311 - bean, err := RecordToBean(record, atURI) 312 - if err != nil { 313 - t.Fatalf("RecordToBean() error = %v", err) 314 - } 315 - 316 - if bean.RKey != "bean123" { 317 - t.Errorf("RKey = %v, want %v", bean.RKey, "bean123") 318 - } 319 - if bean.Name != "Ethiopian Yirgacheffe" { 320 - t.Errorf("Name = %v, want %v", bean.Name, "Ethiopian Yirgacheffe") 321 - } 322 - if bean.Origin != "Ethiopia" { 323 - t.Errorf("Origin = %v, want %v", bean.Origin, "Ethiopia") 324 - } 325 - if bean.RoastLevel != "Light" { 326 - t.Errorf("RoastLevel = %v, want %v", bean.RoastLevel, "Light") 327 - } 328 - if bean.Process != "Washed" { 329 - t.Errorf("Process = %v, want %v", bean.Process, "Washed") 330 - } 331 - if bean.Description != "Fruity notes" { 332 - t.Errorf("Description = %v, want %v", bean.Description, "Fruity notes") 333 - } 153 + bean, err := RecordToBean(record, "at://did:plc:test/social.arabica.alpha.bean/bean123") 154 + require.NoError(t, err) 155 + shutter.Snap(t, "RecordToBean/full record", bean) 334 156 }) 335 157 336 158 t.Run("error without name", func(t *testing.T) { ··· 338 160 "$type": NSIDBean, 339 161 "createdAt": "2025-01-10T12:00:00Z", 340 162 } 341 - 342 163 _, err := RecordToBean(record, "at://did:plc:test/social.arabica.alpha.bean/bean123") 343 - if err == nil { 344 - t.Error("RecordToBean() should error without name") 345 - } 164 + assert.Error(t, err) 346 165 }) 347 166 } 348 167 ··· 356 175 Website: "https://counterculturecoffee.com", 357 176 CreatedAt: createdAt, 358 177 } 359 - 360 178 record, err := RoasterToRecord(roaster) 361 - if err != nil { 362 - t.Fatalf("RoasterToRecord() error = %v", err) 363 - } 364 - 365 - if record["$type"] != NSIDRoaster { 366 - t.Errorf("$type = %v, want %v", record["$type"], NSIDRoaster) 367 - } 368 - if record["name"] != "Counter Culture" { 369 - t.Errorf("name = %v, want %v", record["name"], "Counter Culture") 370 - } 371 - if record["location"] != "Durham, NC" { 372 - t.Errorf("location = %v, want %v", record["location"], "Durham, NC") 373 - } 374 - if record["website"] != "https://counterculturecoffee.com" { 375 - t.Errorf("website = %v, want %v", record["website"], "https://counterculturecoffee.com") 376 - } 179 + require.NoError(t, err) 180 + shutter.Snap(t, "RoasterToRecord/full roaster", record) 377 181 }) 378 182 } 379 183 ··· 386 190 "website": "https://counterculturecoffee.com", 387 191 "createdAt": "2025-01-10T12:00:00Z", 388 192 } 389 - 390 - atURI := "at://did:plc:test/social.arabica.alpha.roaster/roaster123" 391 - roaster, err := RecordToRoaster(record, atURI) 392 - if err != nil { 393 - t.Fatalf("RecordToRoaster() error = %v", err) 394 - } 395 - 396 - if roaster.RKey != "roaster123" { 397 - t.Errorf("RKey = %v, want %v", roaster.RKey, "roaster123") 398 - } 399 - if roaster.Name != "Counter Culture" { 400 - t.Errorf("Name = %v, want %v", roaster.Name, "Counter Culture") 401 - } 402 - if roaster.Location != "Durham, NC" { 403 - t.Errorf("Location = %v, want %v", roaster.Location, "Durham, NC") 404 - } 405 - if roaster.Website != "https://counterculturecoffee.com" { 406 - t.Errorf("Website = %v, want %v", roaster.Website, "https://counterculturecoffee.com") 407 - } 193 + roaster, err := RecordToRoaster(record, "at://did:plc:test/social.arabica.alpha.roaster/roaster123") 194 + require.NoError(t, err) 195 + shutter.Snap(t, "RecordToRoaster/full record", roaster) 408 196 }) 409 197 410 198 t.Run("error without name", func(t *testing.T) { ··· 412 200 "$type": NSIDRoaster, 413 201 "createdAt": "2025-01-10T12:00:00Z", 414 202 } 415 - 416 203 _, err := RecordToRoaster(record, "at://did:plc:test/social.arabica.alpha.roaster/roaster123") 417 - if err == nil { 418 - t.Error("RecordToRoaster() should error without name") 419 - } 204 + assert.Error(t, err) 420 205 }) 421 206 } 422 207 ··· 431 216 Notes: "Great for travel", 432 217 CreatedAt: createdAt, 433 218 } 434 - 435 219 record, err := GrinderToRecord(grinder) 436 - if err != nil { 437 - t.Fatalf("GrinderToRecord() error = %v", err) 438 - } 439 - 440 - if record["$type"] != NSIDGrinder { 441 - t.Errorf("$type = %v, want %v", record["$type"], NSIDGrinder) 442 - } 443 - if record["name"] != "Comandante C40" { 444 - t.Errorf("name = %v, want %v", record["name"], "Comandante C40") 445 - } 446 - if record["grinderType"] != "Hand" { 447 - t.Errorf("grinderType = %v, want %v", record["grinderType"], "Hand") 448 - } 449 - if record["burrType"] != "Conical" { 450 - t.Errorf("burrType = %v, want %v", record["burrType"], "Conical") 451 - } 452 - if record["notes"] != "Great for travel" { 453 - t.Errorf("notes = %v, want %v", record["notes"], "Great for travel") 454 - } 220 + require.NoError(t, err) 221 + shutter.Snap(t, "GrinderToRecord/full grinder", record) 455 222 }) 456 223 } 457 224 ··· 465 232 "notes": "Great for travel", 466 233 "createdAt": "2025-01-10T12:00:00Z", 467 234 } 468 - 469 - atURI := "at://did:plc:test/social.arabica.alpha.grinder/grinder123" 470 - grinder, err := RecordToGrinder(record, atURI) 471 - if err != nil { 472 - t.Fatalf("RecordToGrinder() error = %v", err) 473 - } 474 - 475 - if grinder.RKey != "grinder123" { 476 - t.Errorf("RKey = %v, want %v", grinder.RKey, "grinder123") 477 - } 478 - if grinder.Name != "Comandante C40" { 479 - t.Errorf("Name = %v, want %v", grinder.Name, "Comandante C40") 480 - } 481 - if grinder.GrinderType != "Hand" { 482 - t.Errorf("GrinderType = %v, want %v", grinder.GrinderType, "Hand") 483 - } 484 - if grinder.BurrType != "Conical" { 485 - t.Errorf("BurrType = %v, want %v", grinder.BurrType, "Conical") 486 - } 487 - if grinder.Notes != "Great for travel" { 488 - t.Errorf("Notes = %v, want %v", grinder.Notes, "Great for travel") 489 - } 235 + grinder, err := RecordToGrinder(record, "at://did:plc:test/social.arabica.alpha.grinder/grinder123") 236 + require.NoError(t, err) 237 + shutter.Snap(t, "RecordToGrinder/full record", grinder) 490 238 }) 491 239 } 492 240 ··· 499 247 Description: "Pour-over dripper", 500 248 CreatedAt: createdAt, 501 249 } 502 - 503 250 record, err := BrewerToRecord(brewer) 504 - if err != nil { 505 - t.Fatalf("BrewerToRecord() error = %v", err) 506 - } 507 - 508 - if record["$type"] != NSIDBrewer { 509 - t.Errorf("$type = %v, want %v", record["$type"], NSIDBrewer) 510 - } 511 - if record["name"] != "Hario V60" { 512 - t.Errorf("name = %v, want %v", record["name"], "Hario V60") 513 - } 514 - if record["description"] != "Pour-over dripper" { 515 - t.Errorf("description = %v, want %v", record["description"], "Pour-over dripper") 516 - } 251 + require.NoError(t, err) 252 + shutter.Snap(t, "BrewerToRecord/full brewer", record) 517 253 }) 518 254 } 519 255 ··· 525 261 "description": "Pour-over dripper", 526 262 "createdAt": "2025-01-10T12:00:00Z", 527 263 } 528 - 529 - atURI := "at://did:plc:test/social.arabica.alpha.brewer/brewer123" 530 - brewer, err := RecordToBrewer(record, atURI) 531 - if err != nil { 532 - t.Fatalf("RecordToBrewer() error = %v", err) 533 - } 534 - 535 - if brewer.RKey != "brewer123" { 536 - t.Errorf("RKey = %v, want %v", brewer.RKey, "brewer123") 537 - } 538 - if brewer.Name != "Hario V60" { 539 - t.Errorf("Name = %v, want %v", brewer.Name, "Hario V60") 540 - } 541 - if brewer.Description != "Pour-over dripper" { 542 - t.Errorf("Description = %v, want %v", brewer.Description, "Pour-over dripper") 543 - } 264 + brewer, err := RecordToBrewer(record, "at://did:plc:test/social.arabica.alpha.brewer/brewer123") 265 + require.NoError(t, err) 266 + shutter.Snap(t, "RecordToBrewer/full record", brewer) 544 267 }) 545 268 } 546 269 547 - // TestRoundTrip verifies that converting to record and back preserves data 548 270 func TestRoundTrip(t *testing.T) { 549 271 createdAt := time.Date(2025, 1, 10, 12, 0, 0, 0, time.UTC) 550 272 ··· 557 279 Description: "Fruity notes", 558 280 CreatedAt: createdAt, 559 281 } 560 - 561 282 record, err := BeanToRecord(original, "") 562 - if err != nil { 563 - t.Fatalf("BeanToRecord() error = %v", err) 564 - } 565 - 283 + require.NoError(t, err) 566 284 restored, err := RecordToBean(record, "at://did:plc:test/social.arabica.alpha.bean/bean123") 567 - if err != nil { 568 - t.Fatalf("RecordToBean() error = %v", err) 569 - } 570 - 571 - if restored.Name != original.Name { 572 - t.Errorf("Name = %v, want %v", restored.Name, original.Name) 573 - } 574 - if restored.Origin != original.Origin { 575 - t.Errorf("Origin = %v, want %v", restored.Origin, original.Origin) 576 - } 577 - if restored.RoastLevel != original.RoastLevel { 578 - t.Errorf("RoastLevel = %v, want %v", restored.RoastLevel, original.RoastLevel) 579 - } 285 + require.NoError(t, err) 286 + shutter.Snap(t, "RoundTrip/bean", restored) 580 287 }) 581 288 582 289 t.Run("roaster round trip", func(t *testing.T) { ··· 586 293 Website: "https://counterculturecoffee.com", 587 294 CreatedAt: createdAt, 588 295 } 589 - 590 296 record, err := RoasterToRecord(original) 591 - if err != nil { 592 - t.Fatalf("RoasterToRecord() error = %v", err) 593 - } 594 - 297 + require.NoError(t, err) 595 298 restored, err := RecordToRoaster(record, "at://did:plc:test/social.arabica.alpha.roaster/roaster123") 596 - if err != nil { 597 - t.Fatalf("RecordToRoaster() error = %v", err) 598 - } 599 - 600 - if restored.Name != original.Name { 601 - t.Errorf("Name = %v, want %v", restored.Name, original.Name) 602 - } 603 - if restored.Location != original.Location { 604 - t.Errorf("Location = %v, want %v", restored.Location, original.Location) 605 - } 606 - if restored.Website != original.Website { 607 - t.Errorf("Website = %v, want %v", restored.Website, original.Website) 608 - } 299 + require.NoError(t, err) 300 + shutter.Snap(t, "RoundTrip/roaster", restored) 609 301 }) 610 302 611 303 t.Run("grinder round trip", func(t *testing.T) { ··· 616 308 Notes: "Great for travel", 617 309 CreatedAt: createdAt, 618 310 } 619 - 620 311 record, err := GrinderToRecord(original) 621 - if err != nil { 622 - t.Fatalf("GrinderToRecord() error = %v", err) 623 - } 624 - 312 + require.NoError(t, err) 625 313 restored, err := RecordToGrinder(record, "at://did:plc:test/social.arabica.alpha.grinder/grinder123") 626 - if err != nil { 627 - t.Fatalf("RecordToGrinder() error = %v", err) 628 - } 629 - 630 - if restored.Name != original.Name { 631 - t.Errorf("Name = %v, want %v", restored.Name, original.Name) 632 - } 633 - if restored.GrinderType != original.GrinderType { 634 - t.Errorf("GrinderType = %v, want %v", restored.GrinderType, original.GrinderType) 635 - } 636 - if restored.BurrType != original.BurrType { 637 - t.Errorf("BurrType = %v, want %v", restored.BurrType, original.BurrType) 638 - } 314 + require.NoError(t, err) 315 + shutter.Snap(t, "RoundTrip/grinder", restored) 639 316 }) 640 317 641 318 t.Run("brewer round trip", func(t *testing.T) { ··· 645 322 Description: "Pour-over dripper", 646 323 CreatedAt: createdAt, 647 324 } 648 - 649 325 record, err := BrewerToRecord(original) 650 - if err != nil { 651 - t.Fatalf("BrewerToRecord() error = %v", err) 652 - } 653 - 326 + require.NoError(t, err) 654 327 restored, err := RecordToBrewer(record, "at://did:plc:test/social.arabica.alpha.brewer/brewer123") 655 - if err != nil { 656 - t.Fatalf("RecordToBrewer() error = %v", err) 657 - } 658 - 659 - if restored.Name != original.Name { 660 - t.Errorf("Name = %v, want %v", restored.Name, original.Name) 661 - } 662 - if restored.BrewerType != original.BrewerType { 663 - t.Errorf("BrewerType = %v, want %v", restored.BrewerType, original.BrewerType) 664 - } 665 - if restored.Description != original.Description { 666 - t.Errorf("Description = %v, want %v", restored.Description, original.Description) 667 - } 328 + require.NoError(t, err) 329 + shutter.Snap(t, "RoundTrip/brewer", restored) 668 330 }) 669 331 } 670 332 671 333 func TestTemperatureConversion(t *testing.T) { 672 - // Test temperature encoding/decoding edge cases 673 334 tests := []struct { 674 335 name string 675 336 tempFloat float64 ··· 684 345 685 346 for _, tt := range tests { 686 347 t.Run(tt.name, func(t *testing.T) { 687 - createdAt := time.Now() 688 348 brew := &models.Brew{ 689 349 Temperature: tt.tempFloat, 690 - CreatedAt: createdAt, 350 + CreatedAt: time.Date(2025, 1, 10, 12, 0, 0, 0, time.UTC), 691 351 } 692 352 693 353 record, err := BrewToRecord(brew, "at://did:plc:test/social.arabica.alpha.bean/bean123", "", "", "") 694 - if err != nil { 695 - t.Fatalf("BrewToRecord() error = %v", err) 696 - } 354 + require.NoError(t, err) 697 355 698 - // Check encoding 699 356 if tt.tempFloat > 0 { 700 357 encoded, ok := record["temperature"].(int) 701 - if !ok { 702 - t.Fatalf("temperature should be int, got %T", record["temperature"]) 703 - } 704 - if encoded != tt.tempEncoded { 705 - t.Errorf("encoded temperature = %v, want %v", encoded, tt.tempEncoded) 706 - } 707 - } 358 + require.True(t, ok, "temperature should be int in record") 359 + assert.Equal(t, tt.tempEncoded, encoded) 708 360 709 - // Check decoding 710 - if tt.tempFloat > 0 { 711 - record["temperature"] = float64(tt.tempEncoded) // Simulate JSON unmarshaling 361 + record["temperature"] = float64(tt.tempEncoded) 712 362 restored, err := RecordToBrew(record, "at://did:plc:test/social.arabica.alpha.brew/brew123") 713 - if err != nil { 714 - t.Fatalf("RecordToBrew() error = %v", err) 715 - } 716 - if restored.Temperature != tt.tempFloat { 717 - t.Errorf("decoded temperature = %v, want %v", restored.Temperature, tt.tempFloat) 718 - } 363 + require.NoError(t, err) 364 + assert.InDelta(t, tt.tempFloat, restored.Temperature, 0.001) 719 365 } 720 366 }) 721 367 } ··· 726 372 BeanRKey: "abc123", 727 373 Temperature: 93.5, 728 374 Rating: 8, 729 - CreatedAt: time.Now().Truncate(time.Second), 375 + CreatedAt: time.Date(2025, 1, 10, 12, 0, 0, 0, time.UTC), 730 376 EspressoParams: &models.EspressoParams{ 731 377 YieldWeight: 36.0, 732 378 Pressure: 9.0, ··· 735 381 } 736 382 737 383 record, err := BrewToRecord(original, "at://did:plc:test/social.arabica.alpha.bean/abc123", "", "", "") 738 - assert.NoError(t, err) 739 - 740 - // Verify espressoParams is in the record 741 - ep, ok := record["espressoParams"].(map[string]any) 742 - assert.True(t, ok) 743 - assert.Equal(t, 360, ep["yieldWeight"]) // 36.0 * 10 744 - assert.Equal(t, 90, ep["pressure"]) // 9.0 * 10 745 - assert.Equal(t, 5, ep["preInfusionSeconds"]) 384 + require.NoError(t, err) 385 + shutter.Snap(t, "BrewToRecord/espresso params", record) 746 386 747 387 restored, err := RecordToBrew(record, "at://did:plc:test/social.arabica.alpha.brew/tid123") 748 - assert.NoError(t, err) 749 - assert.NotNil(t, restored.EspressoParams) 750 - assert.InDelta(t, 36.0, restored.EspressoParams.YieldWeight, 0.1) 751 - assert.InDelta(t, 9.0, restored.EspressoParams.Pressure, 0.1) 752 - assert.Equal(t, 5, restored.EspressoParams.PreInfusionSeconds) 388 + require.NoError(t, err) 389 + shutter.Snap(t, "RecordToBrew/espresso params", restored) 753 390 } 754 391 755 392 func TestBrewRoundTrip_PouroverParams(t *testing.T) { 756 393 original := &models.Brew{ 757 394 BeanRKey: "abc123", 758 - CreatedAt: time.Now().Truncate(time.Second), 395 + CreatedAt: time.Date(2025, 1, 10, 12, 0, 0, 0, time.UTC), 759 396 PouroverParams: &models.PouroverParams{ 760 397 BloomWater: 50, 761 398 BloomSeconds: 45, ··· 766 403 } 767 404 768 405 record, err := BrewToRecord(original, "at://did:plc:test/social.arabica.alpha.bean/abc123", "", "", "") 769 - assert.NoError(t, err) 770 - 771 - pp, ok := record["pouroverParams"].(map[string]any) 772 - assert.True(t, ok) 773 - assert.Equal(t, 50, pp["bloomWater"]) 774 - assert.Equal(t, 45, pp["bloomSeconds"]) 775 - assert.Equal(t, 30, pp["drawdownSeconds"]) 776 - assert.Equal(t, 100, pp["bypassWater"]) 777 - assert.Equal(t, "paper", pp["filter"]) 406 + require.NoError(t, err) 407 + shutter.Snap(t, "BrewToRecord/pourover params", record) 778 408 779 409 restored, err := RecordToBrew(record, "at://did:plc:test/social.arabica.alpha.brew/tid123") 780 - assert.NoError(t, err) 781 - assert.NotNil(t, restored.PouroverParams) 782 - assert.Equal(t, 50, restored.PouroverParams.BloomWater) 783 - assert.Equal(t, 45, restored.PouroverParams.BloomSeconds) 784 - assert.Equal(t, 30, restored.PouroverParams.DrawdownSeconds) 785 - assert.Equal(t, 100, restored.PouroverParams.BypassWater) 786 - assert.Equal(t, "paper", restored.PouroverParams.Filter) 410 + require.NoError(t, err) 411 + shutter.Snap(t, "RecordToBrew/pourover params", restored) 787 412 }
+1 -1
nix/default.nix
··· 4 4 pname = "arabica"; 5 5 version = "0.1.0"; 6 6 src = ../.; 7 - vendorHash = "sha256-WyxF5rkiA8vMu0wwbnLyamfKY/+Axi7KXV5TSz6ii2c="; 7 + vendorHash = "sha256-7efW7ct+HVsyxQApb3ratn6eD6VHNDjCPOdwDOjDlSg="; 8 8 9 9 nativeBuildInputs = [ templ tailwindcss ]; 10 10