mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-01-22 18:30:09 +00:00
style: apply prettier formatting
This commit is contained in:
parent
ce91c8b1dc
commit
038a722ed8
5 changed files with 36 additions and 69 deletions
|
|
@ -47,7 +47,6 @@ npm run test:file <filepath>
|
|||
|
||||
- Unit tests: `npm test` - Uses Jasmine/Karma, tests are co-located with source files (`.spec.ts`)
|
||||
- E2E tests: `npm run e2e` - Uses Playwright, located in `/e2e/tests/`
|
||||
|
||||
- `npm run e2e` - Run all tests with minimal output (shows failures clearly)
|
||||
- `npm run e2e:file <path>` - Run a single test file with detailed output
|
||||
- Example: `npm run e2e:file tests/work-view/work-view.spec.ts`
|
||||
|
|
|
|||
|
|
@ -401,13 +401,11 @@ When encryption is enabled on one client:
|
|||
## Testing Strategy
|
||||
|
||||
1. **Unit tests** for `OperationEncryptionService`
|
||||
|
||||
- Encrypt/decrypt round-trips with various payload types
|
||||
- Non-encrypted ops pass through unchanged
|
||||
- Wrong password throws DecryptError
|
||||
|
||||
2. **Integration tests** for upload/download
|
||||
|
||||
- Encrypted operations sync correctly
|
||||
- Mixed encrypted/unencrypted history works
|
||||
- Piggybacked operations decrypt correctly
|
||||
|
|
|
|||
|
|
@ -507,12 +507,10 @@ All phases have been implemented as of December 2025:
|
|||
### ✅ Phase 1: Core Infrastructure (Complete)
|
||||
|
||||
1. **Types** (`operation.types.ts`):
|
||||
|
||||
- `HybridManifest`, `SnapshotReference`, `OperationFileReference` interfaces defined
|
||||
- Backward compatibility maintained with existing `OperationLogManifest`
|
||||
|
||||
2. **Manifest Handling** (`operation-log-manifest.service.ts`):
|
||||
|
||||
- `loadManifest()` handles v1 and v2 formats
|
||||
- Automatic v1 to v2 migration on first write
|
||||
- Buffer/overflow logic in upload services
|
||||
|
|
@ -524,7 +522,6 @@ All phases have been implemented as of December 2025:
|
|||
### ✅ Phase 2: Snapshot Support (Complete)
|
||||
|
||||
4. **Snapshot Operations** (in `operation-log-upload.service.ts` and `operation-log-download.service.ts`):
|
||||
|
||||
- Snapshot generation with current state serialization
|
||||
- Upload with retry logic
|
||||
- Download + validate + apply
|
||||
|
|
@ -536,7 +533,6 @@ All phases have been implemented as of December 2025:
|
|||
### ✅ Phase 3: Robustness (Complete)
|
||||
|
||||
6. **Concurrency Control**:
|
||||
|
||||
- Provider-specific revision checking (Dropbox rev, WebDAV ETag)
|
||||
- Retry-on-conflict logic implemented
|
||||
|
||||
|
|
|
|||
|
|
@ -912,19 +912,16 @@ Remote Op (v1) Local Op (v2)
|
|||
When deploying a schema migration:
|
||||
|
||||
1. **Release new version with migration code**
|
||||
|
||||
- Add migration to `MIGRATIONS` array
|
||||
- Bump `CURRENT_SCHEMA_VERSION`
|
||||
- Migration handles both state and operations
|
||||
|
||||
2. **Graceful degradation period**
|
||||
|
||||
- Old clients continue working (they don't know about new schema)
|
||||
- New clients migrate incoming old ops seamlessly
|
||||
- Mixed-version sync works via receiver-side migration
|
||||
|
||||
3. **Monitoring** (future)
|
||||
|
||||
- Track `op.schemaVersion` distribution in server logs
|
||||
- Alert if many clients are > 2 versions behind
|
||||
|
||||
|
|
@ -988,13 +985,11 @@ export const MIGRATIONS: SchemaMigration[] = [
|
|||
Before releasing any migration:
|
||||
|
||||
1. **Unit tests** in `schema-migration.service.spec.ts`:
|
||||
|
||||
- State migration correctness
|
||||
- Operation migration correctness
|
||||
- Null return for dropped operations
|
||||
|
||||
2. **Integration tests** in `cross-version-sync.integration.spec.ts`:
|
||||
|
||||
- Client A (v1) syncs with Client B (v2)
|
||||
- Both clients converge to same state
|
||||
- No data loss during migration
|
||||
|
|
@ -1729,19 +1724,16 @@ When IndexedDB storage quota is exceeded, the system handles it gracefully:
|
|||
**Implementation** (see `operation-log.effects.ts`):
|
||||
|
||||
1. **Error Detection**: Catches `QuotaExceededError` including browser variants:
|
||||
|
||||
- Standard: `DOMException` with name `QuotaExceededError`
|
||||
- Firefox: `NS_ERROR_DOM_QUOTA_REACHED`
|
||||
- Safari (legacy): Error code 22
|
||||
|
||||
2. **Emergency Compaction**: Triggers `emergencyCompact()` with shorter retention:
|
||||
|
||||
- Normal retention: 7 days (`COMPACTION_RETENTION_MS`)
|
||||
- Emergency retention: 24 hours (`EMERGENCY_COMPACTION_RETENTION_MS`)
|
||||
- Only deletes ops that have been synced (`syncedAt` set)
|
||||
|
||||
3. **Circuit Breaker**: Flag `isHandlingQuotaExceeded` prevents infinite retry loops:
|
||||
|
||||
- If quota exceeded during retry attempt, aborts immediately
|
||||
- Shows error to user instead of looping forever
|
||||
|
||||
|
|
|
|||
|
|
@ -286,9 +286,8 @@ describe('Storage Quota Cleanup', () => {
|
|||
describe('deleteOldestRestorePointAndOps', () => {
|
||||
it('should return failure when no restore points exist', async () => {
|
||||
// Import after mocks are set up
|
||||
const { initSyncService, getSyncService } = await import(
|
||||
'../src/sync/sync.service'
|
||||
);
|
||||
const { initSyncService, getSyncService } =
|
||||
await import('../src/sync/sync.service');
|
||||
initSyncService();
|
||||
const service = getSyncService();
|
||||
|
||||
|
|
@ -305,9 +304,8 @@ describe('Storage Quota Cleanup', () => {
|
|||
});
|
||||
|
||||
it('should delete oldest restore point and all ops before it when 2+ restore points exist', async () => {
|
||||
const { initSyncService, getSyncService } = await import(
|
||||
'../src/sync/sync.service'
|
||||
);
|
||||
const { initSyncService, getSyncService } =
|
||||
await import('../src/sync/sync.service');
|
||||
initSyncService();
|
||||
const service = getSyncService();
|
||||
|
||||
|
|
@ -330,9 +328,8 @@ describe('Storage Quota Cleanup', () => {
|
|||
});
|
||||
|
||||
it('should keep single restore point but delete ops before it', async () => {
|
||||
const { initSyncService, getSyncService } = await import(
|
||||
'../src/sync/sync.service'
|
||||
);
|
||||
const { initSyncService, getSyncService } =
|
||||
await import('../src/sync/sync.service');
|
||||
initSyncService();
|
||||
const service = getSyncService();
|
||||
|
||||
|
|
@ -353,9 +350,8 @@ describe('Storage Quota Cleanup', () => {
|
|||
});
|
||||
|
||||
it('should return failure when restore point is at seq 1 with no ops before it', async () => {
|
||||
const { initSyncService, getSyncService } = await import(
|
||||
'../src/sync/sync.service'
|
||||
);
|
||||
const { initSyncService, getSyncService } =
|
||||
await import('../src/sync/sync.service');
|
||||
initSyncService();
|
||||
const service = getSyncService();
|
||||
|
||||
|
|
@ -375,9 +371,8 @@ describe('Storage Quota Cleanup', () => {
|
|||
});
|
||||
|
||||
it('should handle BACKUP_IMPORT as restore point', async () => {
|
||||
const { initSyncService, getSyncService } = await import(
|
||||
'../src/sync/sync.service'
|
||||
);
|
||||
const { initSyncService, getSyncService } =
|
||||
await import('../src/sync/sync.service');
|
||||
initSyncService();
|
||||
const service = getSyncService();
|
||||
|
||||
|
|
@ -393,9 +388,8 @@ describe('Storage Quota Cleanup', () => {
|
|||
});
|
||||
|
||||
it('should handle REPAIR as restore point', async () => {
|
||||
const { initSyncService, getSyncService } = await import(
|
||||
'../src/sync/sync.service'
|
||||
);
|
||||
const { initSyncService, getSyncService } =
|
||||
await import('../src/sync/sync.service');
|
||||
initSyncService();
|
||||
const service = getSyncService();
|
||||
|
||||
|
|
@ -413,9 +407,8 @@ describe('Storage Quota Cleanup', () => {
|
|||
|
||||
describe('Storage quota with auto-cleanup', () => {
|
||||
it('should recalculate storage when quota check fails', async () => {
|
||||
const { initSyncService, getSyncService } = await import(
|
||||
'../src/sync/sync.service'
|
||||
);
|
||||
const { initSyncService, getSyncService } =
|
||||
await import('../src/sync/sync.service');
|
||||
initSyncService();
|
||||
const service = getSyncService();
|
||||
|
||||
|
|
@ -434,9 +427,8 @@ describe('Storage Quota Cleanup', () => {
|
|||
});
|
||||
|
||||
it('should allow upload after storage recalculation shows actual usage is lower', async () => {
|
||||
const { initSyncService, getSyncService } = await import(
|
||||
'../src/sync/sync.service'
|
||||
);
|
||||
const { initSyncService, getSyncService } =
|
||||
await import('../src/sync/sync.service');
|
||||
initSyncService();
|
||||
const service = getSyncService();
|
||||
|
||||
|
|
@ -457,9 +449,8 @@ describe('Storage Quota Cleanup', () => {
|
|||
|
||||
describe('freeStorageForUpload - iterative cleanup', () => {
|
||||
it('should return success immediately if quota is already satisfied', async () => {
|
||||
const { initSyncService, getSyncService } = await import(
|
||||
'../src/sync/sync.service'
|
||||
);
|
||||
const { initSyncService, getSyncService } =
|
||||
await import('../src/sync/sync.service');
|
||||
initSyncService();
|
||||
const service = getSyncService();
|
||||
|
||||
|
|
@ -480,9 +471,8 @@ describe('Storage Quota Cleanup', () => {
|
|||
});
|
||||
|
||||
it('should return failure when no restore points exist and quota exceeded', async () => {
|
||||
const { initSyncService, getSyncService } = await import(
|
||||
'../src/sync/sync.service'
|
||||
);
|
||||
const { initSyncService, getSyncService } =
|
||||
await import('../src/sync/sync.service');
|
||||
initSyncService();
|
||||
const service = getSyncService();
|
||||
|
||||
|
|
@ -507,9 +497,8 @@ describe('Storage Quota Cleanup', () => {
|
|||
});
|
||||
|
||||
it('should return failure when only one restore point exists', async () => {
|
||||
const { initSyncService, getSyncService } = await import(
|
||||
'../src/sync/sync.service'
|
||||
);
|
||||
const { initSyncService, getSyncService } =
|
||||
await import('../src/sync/sync.service');
|
||||
initSyncService();
|
||||
const service = getSyncService();
|
||||
|
||||
|
|
@ -535,9 +524,8 @@ describe('Storage Quota Cleanup', () => {
|
|||
});
|
||||
|
||||
it('should delete multiple restore points iteratively until quota is satisfied', async () => {
|
||||
const { initSyncService, getSyncService } = await import(
|
||||
'../src/sync/sync.service'
|
||||
);
|
||||
const { initSyncService, getSyncService } =
|
||||
await import('../src/sync/sync.service');
|
||||
initSyncService();
|
||||
const service = getSyncService();
|
||||
|
||||
|
|
@ -597,9 +585,8 @@ describe('Storage Quota Cleanup', () => {
|
|||
});
|
||||
|
||||
it('should stop when only one restore point remains even if quota still exceeded', async () => {
|
||||
const { initSyncService, getSyncService } = await import(
|
||||
'../src/sync/sync.service'
|
||||
);
|
||||
const { initSyncService, getSyncService } =
|
||||
await import('../src/sync/sync.service');
|
||||
initSyncService();
|
||||
const service = getSyncService();
|
||||
|
||||
|
|
@ -636,9 +623,8 @@ describe('Storage Quota Cleanup', () => {
|
|||
});
|
||||
|
||||
it('should return stats even when cleanup fails', async () => {
|
||||
const { initSyncService, getSyncService } = await import(
|
||||
'../src/sync/sync.service'
|
||||
);
|
||||
const { initSyncService, getSyncService } =
|
||||
await import('../src/sync/sync.service');
|
||||
initSyncService();
|
||||
const service = getSyncService();
|
||||
|
||||
|
|
@ -667,9 +653,8 @@ describe('Storage Quota Cleanup', () => {
|
|||
|
||||
describe('Stale snapshot cache cleanup', () => {
|
||||
it('should clear snapshot cache when deleted ops include the cached snapshot seq', async () => {
|
||||
const { initSyncService, getSyncService } = await import(
|
||||
'../src/sync/sync.service'
|
||||
);
|
||||
const { initSyncService, getSyncService } =
|
||||
await import('../src/sync/sync.service');
|
||||
const { prisma } = await import('../src/db');
|
||||
initSyncService();
|
||||
const service = getSyncService();
|
||||
|
|
@ -708,9 +693,8 @@ describe('Storage Quota Cleanup', () => {
|
|||
});
|
||||
|
||||
it('should NOT clear snapshot cache when cached seq is after deleted ops', async () => {
|
||||
const { initSyncService, getSyncService } = await import(
|
||||
'../src/sync/sync.service'
|
||||
);
|
||||
const { initSyncService, getSyncService } =
|
||||
await import('../src/sync/sync.service');
|
||||
const { prisma } = await import('../src/db');
|
||||
initSyncService();
|
||||
const service = getSyncService();
|
||||
|
|
@ -749,9 +733,8 @@ describe('Storage Quota Cleanup', () => {
|
|||
});
|
||||
|
||||
it('should clear snapshot cache when cached seq equals deleteUpToSeq exactly', async () => {
|
||||
const { initSyncService, getSyncService } = await import(
|
||||
'../src/sync/sync.service'
|
||||
);
|
||||
const { initSyncService, getSyncService } =
|
||||
await import('../src/sync/sync.service');
|
||||
const { prisma } = await import('../src/db');
|
||||
initSyncService();
|
||||
const service = getSyncService();
|
||||
|
|
@ -789,9 +772,8 @@ describe('Storage Quota Cleanup', () => {
|
|||
});
|
||||
|
||||
it('should not crash when no cached snapshot exists', async () => {
|
||||
const { initSyncService, getSyncService } = await import(
|
||||
'../src/sync/sync.service'
|
||||
);
|
||||
const { initSyncService, getSyncService } =
|
||||
await import('../src/sync/sync.service');
|
||||
initSyncService();
|
||||
const service = getSyncService();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue