Files
e02bd5330d feat(cloud): launch pricing guardrails — egress caps, ignore defaults, storage tiering (#114)
QuotaProvider grows a read half: CheckRead(org, bytes) and RecordEgress(org, bytes). CheckRead is enforced on /s/* only — a public share link is the sole unauthenticated door to stored bytes, so it is the only egress a plan can cap. The sync proxy and viewer merely RecordEgress: refusing a device mid-cycle surfaces as ErrForbidden, which the syncer reads as "access is gone — pause and touch nothing", and sync must never break over a bill. UnlimitedQuota stays the OSS default. countingWriter bills what actually reached the client rather than a size claimed before the write.

bdrive init warns past 1 GiB or 20k files and says how to narrow scope; syncer.Measure sizes that through the real Filter and the one walkFolder predicate. starterIgnore gains video/archive/disk-image globs and Library/ — every version is kept forever, so a big binary committed once is paid for forever on every device.

deploy: a Nearline-at-30-days lifecycle rule and the arithmetic for why it stops there. Coldline and Archive only pay off below roughly one read per month, and a first sync pulls every historical blob rather than just the current tree, so blob read rate tracks device onboarding.

docs/launch-plan.md said Cloud was waitlist-only and framed Product Hunt as an OSS launch whose goal was not signups or revenue; both are stale.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 19:16:42 +09:00

59 lines
2.1 KiB
Go

package syncer
import (
"strings"
"testing"
)
// Measure exists to size the warning `bdrive init` prints, so the one thing
// it must get right is that it counts what would REALLY sync. A folder whose
// bulk is already ignored must measure small, or the warning fires on every
// ordinary repo and people learn to scroll past it.
func TestMeasureCountsOnlyWhatSyncs(t *testing.T) {
d := newDevice(t, "deva", nil)
write(t, d.Folder, IgnoreFile, "node_modules/\n*.mp4\n")
write(t, d.Folder, "notes.md", "hello") // 5 B, syncs
write(t, d.Folder, "docs/guide.md", "guide") // 5 B, syncs
write(t, d.Folder, "node_modules/dep/index.js", strings.Repeat("x", 900)) // ignored dir
write(t, d.Folder, "demo.mp4", strings.Repeat("y", 900)) // ignored glob
write(t, d.Folder, ".git/objects/ab/cdef", strings.Repeat("z", 900)) // never syncs, built in
files, bytes, err := Measure(d.Folder, nil)
if err != nil {
t.Fatal(err)
}
// notes.md + docs/guide.md + the .bdriveignore itself, which does sync.
if files != 3 {
t.Fatalf("files = %d, want 3 (the ignored/reserved paths must not count)", files)
}
if bytes >= 900 {
t.Fatalf("bytes = %d — an ignored 900 B file was counted", bytes)
}
}
// A scope narrowing is written as .bdriveignore negation rules, so Measure
// has to shrink with it: otherwise `bdrive scope add` would never quiet the
// warning it tells you to run.
func TestMeasureShrinksWithScope(t *testing.T) {
d := newDevice(t, "devb", nil)
write(t, d.Folder, ".bdrive/config.json", `{"include":["/docs/"]}`)
write(t, d.Folder, "docs/guide.md", "in scope")
write(t, d.Folder, "private/secrets.txt", strings.Repeat("q", 500))
all, allBytes, err := Measure(d.Folder, nil)
if err != nil {
t.Fatal(err)
}
scoped, scopedBytes, err := Measure(d.Folder, []string{"/docs/"})
if err != nil {
t.Fatal(err)
}
if scoped >= all || scopedBytes >= allBytes {
t.Fatalf("scoped measure (%d files, %d B) did not shrink vs whole folder (%d files, %d B)",
scoped, scopedBytes, all, allBytes)
}
if scopedBytes >= 500 {
t.Fatalf("out-of-scope file counted: %d B", scopedBytes)
}
}