mirror of
https://github.com/hanzoai/base.git
synced 2026-10-21 03:41:50 +00:00
* feat(crm): email + calendar sync data model (connected accounts) Phase 0 of email/calendar CRM sync: the 10-collection data model as a schema migration (same proven pattern as the saved-views collections). - connectedAccount — OAuth provider + handle + scopes + ENCRYPTED tokens (accessTokenEnc/refreshTokenEnc ciphertext only; authFailedAt drives reconnect). - messaging: messageChannel (sync state machine) / messageThread / message (dedup by headerMessageId) / messageParticipant (handle=email → person/user) / messageAssociation (per-channel external id + thread id + dedup). - calendar: calendarChannel / calendarEvent / calendarEventParticipant / calendarEventAssociation (recurringEventExternalId links series instances). Plugin-owned + user-readable: CreateRule/UpdateRule/DeleteRule = nil (only the sync plugin/superuser writes), owner-scoped List/View so the console shows a user's own mail/events. syncStateFields = the channel state machine (status/stage/cursor/throttle) the two-phase list→import engine drives. Relations are forward-compatible text refs (refField) so the schema applies before the person/company object graph exists; a later migration upgrades them. Next phases (plugins): plugins/connect (OAuth + token refresh + email→record matcher), plugins/messaging + plugins/calendar (Google drivers + the two-phase cron sync engine). Compiles clean; CI runs the full migration-boot test. * test(migrations): prove the connect/messaging/calendar model boots Boots a TestApp (which runs SystemMigrations) and asserts all ten collections apply with the intended access model: owner-scoped reads and nil create/update/ delete rules (plugin-only writes). Locks the Phase-0 CRM sync data model against a regression that would leave it world-readable or unwritable by the sync plugin. * test: account for the 10 CRM collections in collection-count assertions Migration 1780500000_connect_messaging_calendar adds 10 base-type collections (connect/messaging/calendar), so the default collection set grows 17 → 27. Update the enumeration/import tests to match: - core TestFindAllCollections: all-types total 17 → 27 (auth=4/view=7 unchanged; the new collections are all type "base"). - apis TestCollectionsList: "totalItems":17 → 27. - apis TestCollectionsImport: totalCollections baseline 17 → 27, and the deleteMissing scenario now deletes 9 + 10 = 19 collections (its delete-event counts 9 → 19). The dynamic systemCollections assertion is unchanged (the CRM collections are non-system). * test(platform): deterministic idle-eviction test (kill sweeper race) TestPoolManager_IdleEviction flaked as 'expected 3 pools, got 2': the setup loop released each pool immediately, so pool 0 went idle and the sweeper could evict it before the Len()==3 assertion on a slow runner. The #25 timeout bump (300ms) only widened the window, not closed it. Fix gates on a deterministic signal, not wall-clock: - Hold all 3 pools IN-USE through the Len()==3 assertion. The sweeper skips refCount>0 pools (evictIdleLocked), so setup can never race it. - After release, poll on IdleEvictions (the sweeper's last, monotonic step) instead of Len(): a pool leaves the LRU under lock before its IdleEvictions++ runs post-unlock, so Len()==0 can be observed while the counter lags. Same hardening applied to _SkipsInUse. - Replace fixed sleeps with a bounded eventually() poll. Verified: 50x -race against dbx.PoolManager (incl. a 60ms/open slow Connect that reproduces the original flake) all green. --------- Co-authored-by: zeekay <[email protected]> Co-authored-by: Hanzo Dev <[email protected]>
40 lines
1.3 KiB
Go
40 lines
1.3 KiB
Go
package migrations_test
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/hanzoai/base/tests"
|
|
)
|
|
|
|
// Proves the connect/messaging/calendar migration applies at boot and that all
|
|
// ten collections exist with the intended access model: owner-scoped reads and
|
|
// plugin-only writes (the sync plugin is the sole writer). Guards against a
|
|
// regression that would leave the CRM sync model unwritable-by-plugin or
|
|
// world-readable.
|
|
func TestConnectMessagingCalendar_BootApplies(t *testing.T) {
|
|
app, err := tests.NewTestApp()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer app.Cleanup()
|
|
|
|
want := []string{
|
|
"connectedAccount",
|
|
"messageChannel", "messageThread", "message", "messageParticipant", "messageAssociation",
|
|
"calendarChannel", "calendarEvent", "calendarEventParticipant", "calendarEventAssociation",
|
|
}
|
|
for _, name := range want {
|
|
c, err := app.FindCollectionByNameOrId(name)
|
|
if err != nil {
|
|
t.Fatalf("collection %q not created by migration: %v", name, err)
|
|
}
|
|
if c.CreateRule != nil || c.UpdateRule != nil || c.DeleteRule != nil {
|
|
t.Errorf("%q must be plugin-only-write (nil create/update/delete rules), got C=%v U=%v D=%v",
|
|
name, c.CreateRule, c.UpdateRule, c.DeleteRule)
|
|
}
|
|
if c.ListRule == nil || *c.ListRule != "owner = @request.auth.id" {
|
|
t.Errorf("%q must be owner-scoped readable, got ListRule=%v", name, c.ListRule)
|
|
}
|
|
}
|
|
}
|