| 619 | } |
| 620 | |
| 621 | func TestCounterInt64_TryUpdate_RollbackLogic(t *testing.T) { |
| 622 | tests := []struct { |
| 623 | name string |
| 624 | setupMocks func(conn *MockConnector, lock *MockLock) |
| 625 | initialValue int64 |
| 626 | updateValue int64 |
| 627 | ignoreRollbackOf int64 |
| 628 | expectedValue int64 |
| 629 | eventual bool |
| 630 | eventualValue int64 |
| 631 | }{ |
| 632 | { |
| 633 | name: "remote lock fails, fallback local, higher update => update", |
| 634 | setupMocks: func(conn *MockConnector, lock *MockLock) { |
| 635 | // No Set call is expected because we fail to lock remotely |
| 636 | conn.On("Lock", mock.Anything, "test", mock.Anything). |
| 637 | Return(nil, errors.New("lock failed")) |
| 638 | // Background push publishes best-effort even when the lock cannot be acquired. |
| 639 | conn.On("PublishCounterInt64", mock.Anything, "test", mock.Anything).Return(nil) |
| 640 | }, |
| 641 | initialValue: 5, |
| 642 | updateValue: 10, |
| 643 | ignoreRollbackOf: 1024, |
| 644 | expectedValue: 10, |
| 645 | }, |
| 646 | { |
| 647 | name: "remote lock fails, fallback local, lower update within rollback range => ignored", |
| 648 | setupMocks: func(conn *MockConnector, lock *MockLock) { |
| 649 | // Small rollback within ignoreRollbackOf is silently ignored (no remote work) |
| 650 | }, |
| 651 | initialValue: 100, |
| 652 | updateValue: 95, |
| 653 | ignoreRollbackOf: 10, // gap=5 <= 10, so rollback is ignored |
| 654 | expectedValue: 100, |
| 655 | }, |
| 656 | { |
| 657 | name: "remote lock fails, fallback local, lower update exceeds rollback range => applied", |
| 658 | setupMocks: func(conn *MockConnector, lock *MockLock) { |
| 659 | // Large rollback exceeding ignoreRollbackOf is APPLIED |
| 660 | conn.On("Lock", mock.Anything, "test", mock.Anything). |
| 661 | Return(nil, errors.New("lock failed")) |
| 662 | conn.On("PublishCounterInt64", mock.Anything, "test", mock.Anything).Return(nil) |
| 663 | }, |
| 664 | initialValue: 100, |
| 665 | updateValue: 50, |
| 666 | ignoreRollbackOf: 40, // gap=50 > 40, so rollback is APPLIED |
| 667 | expectedValue: 50, |
| 668 | }, |
| 669 | { |
| 670 | name: "remote lock succeeds, remote higher => override local; push reconciled value", |
| 671 | setupMocks: func(conn *MockConnector, lock *MockLock) { |
| 672 | conn.On("Lock", mock.Anything, "test", mock.Anything).Return(lock, nil) |
| 673 | lock.On("Unlock", mock.Anything).Return(nil) |
| 674 | conn.On("Get", mock.Anything, ConnectorMainIndex, "test", "value", nil). |
| 675 | Return([]byte(fmt.Sprintf(`{"v":200,"t":%d,"b":"test"}`, time.Now().UnixMilli()+60_000)), nil) |
| 676 | // Background publish is best-effort. |
| 677 | conn.On("PublishCounterInt64", mock.Anything, "test", mock.Anything).Return(nil) |
| 678 | }, |