Skip to content

Commit 8e96db4

Browse files
release: 7.62.2 (#25436)
# 🚀 v7.62.2 Testing & Release Quality Process Hi Team, As part of our new **MetaMask Release Quality Process**, here’s a quick overview of the key processes, testing strategies, and milestones to ensure a smooth and high-quality deployment. --- ## 📋 Key Processes ### Testing Strategy - **Developer Teams:** Conduct regression and exploratory testing for your functional areas, including automated and manual tests for critical workflows. - **QA Team:** Focus on exploratory testing across the wallet, prioritize high-impact areas, and triage any Sentry errors found during testing. - **Customer Success Team:** Validate new functionalities and provide feedback to support release monitoring. ### GitHub Signoff - Each team must **sign off on the Release Candidate (RC)** via GitHub by the end of the validation timeline (**Tuesday EOD PT**). - Ensure all tests outlined in the Testing Plan are executed, and any identified issues are addressed. ### Issue Resolution - **Resolve all Release Blockers** (Sev0 and Sev1) by **Tuesday EOD PT**. - For unresolved blockers, PRs may be reverted, or feature flags disabled to maintain release quality and timelines. ### Cherry-Picking Criteria - Only **critical fixes** meeting outlined criteria will be cherry-picked. - Developers must ensure these fixes are thoroughly reviewed, tested, and merged by **Tuesday EOD PT**. --- ## 🗓️ Timeline and Milestones 1. **Today (Friday):** Begin Release Candidate validation. 2. **Tuesday EOD PT:** Finalize RC with all fixes and cherry-picks. 3. **Wednesday:** Buffer day for final checks. 4. **Thursday:** Submit release to app stores and begin rollout to 1% of users. 5. **Monday:** Scale deployment to 10%. 6. **Tuesday:** Full rollout to 100%. --- ## ✅ Signoff Checklist Each team is responsible for signing off via GitHub. Use the checkbox below to track signoff completion: # Team sign-off checklist - [ ] Mobile Platform This process is a major step forward in ensuring release stability and quality. Let’s stay aligned and make this release a success! 🚀 Feel free to reach out if you have questions or need clarification. Many thanks in advance # Reference - Testing plan sheet - https://docs.google.com/spreadsheets/d/1tsoodlAlyvEUpkkcNcbZ4PM9HuC9cEM80RZeoVv5OCQ/edit?gid=404070372#gid=404070372
2 parents 4a7ca70 + dc3df2a commit 8e96db4

21 files changed

Lines changed: 1617 additions & 339 deletions

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [7.62.2]
11+
12+
### Fixed
13+
14+
- fix: cherry-pick 429 rate limiting fix with coin naming convention (#25443)
15+
- fix(perps): potential rate limit on close positions (#25456)
16+
1017
## [7.62.1]
1118

1219
### Fixed
@@ -9910,7 +9917,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99109917
- [#957](https://github.com/MetaMask/metamask-mobile/pull/957): fix timeouts (#957)
99119918
- [#954](https://github.com/MetaMask/metamask-mobile/pull/954): Bugfix: onboarding navigation (#954)
99129919

9913-
[Unreleased]: https://github.com/MetaMask/metamask-mobile/compare/v7.62.1...HEAD
9920+
[Unreleased]: https://github.com/MetaMask/metamask-mobile/compare/v7.62.2...HEAD
9921+
[7.62.2]: https://github.com/MetaMask/metamask-mobile/compare/v7.62.1...v7.62.2
99149922
[7.62.1]: https://github.com/MetaMask/metamask-mobile/compare/v7.62.0...v7.62.1
99159923
[7.62.0]: https://github.com/MetaMask/metamask-mobile/compare/v7.61.6...v7.62.0
99169924
[7.61.6]: https://github.com/MetaMask/metamask-mobile/compare/v7.61.5...v7.61.6

android/app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,8 +187,8 @@ android {
187187
applicationId "io.metamask"
188188
minSdkVersion rootProject.ext.minSdkVersion
189189
targetSdkVersion rootProject.ext.targetSdkVersion
190-
versionName "7.62.1"
191-
versionCode 3561
190+
versionName "7.62.2"
191+
versionCode 3591
192192
testBuildType System.getProperty('testBuildType', 'debug')
193193
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
194194
manifestPlaceholders.MM_BRANCH_KEY_TEST = "$System.env.MM_BRANCH_KEY_TEST"

app/components/UI/Perps/Perps.testIds.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,8 @@ export const PerpsTutorialSelectorsIDs = {
472472
export const PerpsStopLossPromptSelectorsIDs = {
473473
CONTAINER: 'perps-stop-loss-prompt-container',
474474
ADD_MARGIN_BUTTON: 'perps-stop-loss-prompt-add-margin-button',
475-
TOGGLE: 'perps-stop-loss-prompt-toggle',
475+
SET_STOP_LOSS_BUTTON: 'perps-stop-loss-prompt-set-button',
476+
SUCCESS_ICON: 'perps-stop-loss-prompt-success-icon',
476477
LOADING: 'perps-stop-loss-prompt-loading',
477478
} as const;
478479

app/components/UI/Perps/Views/PerpsMarketDetailsView/PerpsMarketDetailsView.test.tsx

Lines changed: 73 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -252,21 +252,28 @@ jest.mock('../../hooks/usePerpsOpenOrders', () => ({
252252
usePerpsOpenOrders: () => mockUsePerpsOpenOrdersImpl(),
253253
}));
254254

255-
const mockUsePerpsOrderFillsImpl = jest.fn<
255+
const mockUsePerpsLiveFillsImpl = jest.fn<
256256
ReturnType<
257-
typeof import('../../hooks/usePerpsOrderFills').usePerpsOrderFills
257+
typeof import('../../hooks/stream/usePerpsLiveFills').usePerpsLiveFills
258258
>,
259259
[]
260260
>(() => ({
261-
orderFills: [],
262-
isLoading: false,
263-
error: null,
264-
refresh: jest.fn(),
265-
isRefreshing: false,
261+
fills: [],
262+
isInitialLoading: false,
266263
}));
267264

268-
jest.mock('../../hooks/usePerpsOrderFills', () => ({
269-
usePerpsOrderFills: () => mockUsePerpsOrderFillsImpl(),
265+
jest.mock('../../hooks/stream/usePerpsLiveFills', () => ({
266+
usePerpsLiveFills: () => mockUsePerpsLiveFillsImpl(),
267+
}));
268+
269+
// Mock Engine for REST fallback tests
270+
const mockGetOrderFills = jest.fn();
271+
jest.mock('../../../../../core/Engine', () => ({
272+
context: {
273+
PerpsController: {
274+
getOrderFills: (...args: unknown[]) => mockGetOrderFills(...args),
275+
},
276+
},
270277
}));
271278

272279
// Mock for usePerpsMarkets that can be modified per test
@@ -568,6 +575,7 @@ describe('PerpsMarketDetailsView', () => {
568575
error: null,
569576
existingPosition: null,
570577
refreshPosition: jest.fn(),
578+
positionOpenedTimestamp: undefined,
571579
});
572580

573581
// Reset navigation mocks
@@ -600,12 +608,9 @@ describe('PerpsMarketDetailsView', () => {
600608
};
601609

602610
// Reset order fills mock to default
603-
mockUsePerpsOrderFillsImpl.mockReturnValue({
604-
orderFills: [],
605-
isLoading: false,
606-
error: null,
607-
refresh: jest.fn(),
608-
isRefreshing: false,
611+
mockUsePerpsLiveFillsImpl.mockReturnValue({
612+
fills: [],
613+
isInitialLoading: false,
609614
});
610615
});
611616

@@ -834,6 +839,7 @@ describe('PerpsMarketDetailsView', () => {
834839
},
835840
},
836841
refreshPosition: jest.fn(),
842+
positionOpenedTimestamp: undefined,
837843
});
838844

839845
const { getByTestId, queryByText, queryByTestId } = renderWithProvider(
@@ -924,6 +930,7 @@ describe('PerpsMarketDetailsView', () => {
924930
error: null,
925931
existingPosition: null,
926932
refreshPosition: mockRefreshPosition, // No-op function for WebSocket positions
933+
positionOpenedTimestamp: undefined,
927934
});
928935

929936
const { getByTestId } = renderWithProvider(
@@ -975,6 +982,7 @@ describe('PerpsMarketDetailsView', () => {
975982
},
976983
},
977984
refreshPosition: mockRefreshPosition,
985+
positionOpenedTimestamp: undefined,
978986
});
979987

980988
const { getByTestId } = renderWithProvider(
@@ -1011,6 +1019,7 @@ describe('PerpsMarketDetailsView', () => {
10111019
error: null,
10121020
existingPosition: null,
10131021
refreshPosition: mockRefreshPosition,
1022+
positionOpenedTimestamp: undefined,
10141023
});
10151024

10161025
const { getByTestId } = renderWithProvider(
@@ -1075,6 +1084,7 @@ describe('PerpsMarketDetailsView', () => {
10751084
error: null,
10761085
existingPosition: null,
10771086
refreshPosition: mockRefreshPosition,
1087+
positionOpenedTimestamp: undefined,
10781088
});
10791089

10801090
const { getByTestId } = renderWithProvider(
@@ -1692,10 +1702,13 @@ describe('PerpsMarketDetailsView', () => {
16921702
});
16931703
});
16941704

1695-
describe('Position opened timestamp calculation', () => {
1696-
it('computes position opened timestamp from order fills data', () => {
1697-
// Arrange
1698-
const timestamp = Date.now();
1705+
describe('Position opened timestamp', () => {
1706+
// Note: The timestamp calculation logic has been moved to useHasExistingPosition hook
1707+
// These tests verify the component correctly uses the hook's positionOpenedTimestamp
1708+
1709+
it('uses positionOpenedTimestamp from useHasExistingPosition hook', () => {
1710+
// Arrange - Hook provides the timestamp
1711+
const timestamp = Date.now() - 5 * 60 * 1000;
16991712
mockUseHasExistingPosition.mockReturnValue({
17001713
hasPosition: true,
17011714
isLoading: false,
@@ -1718,51 +1731,50 @@ describe('PerpsMarketDetailsView', () => {
17181731
},
17191732
},
17201733
refreshPosition: jest.fn(),
1734+
positionOpenedTimestamp: timestamp, // Hook now provides this
17211735
});
17221736

1723-
mockUsePerpsOrderFillsImpl.mockReturnValue({
1724-
orderFills: [
1725-
{
1726-
orderId: 'order-1',
1727-
symbol: 'BTC',
1728-
side: 'buy',
1729-
direction: 'Open Long',
1730-
timestamp: timestamp - 2000,
1731-
size: '0.3',
1732-
price: '43000',
1733-
pnl: '0',
1734-
fee: '0.001',
1735-
feeToken: 'USDC',
1736-
},
1737-
{
1738-
orderId: 'order-2',
1739-
symbol: 'BTC',
1740-
side: 'buy',
1741-
direction: 'Open Long',
1742-
timestamp,
1743-
size: '0.5',
1744-
price: '44000',
1745-
pnl: '0',
1746-
fee: '0.001',
1747-
feeToken: 'USDC',
1748-
},
1749-
{
1750-
orderId: 'order-3',
1751-
symbol: 'ETH',
1752-
side: 'sell',
1753-
direction: 'Open Short',
1754-
timestamp: timestamp - 1000,
1755-
size: '1.0',
1756-
price: '3000',
1757-
pnl: '0',
1758-
fee: '0.001',
1759-
feeToken: 'USDC',
1760-
},
1761-
],
1737+
// Act
1738+
const { getByTestId } = renderWithProvider(
1739+
<PerpsConnectionProvider>
1740+
<PerpsMarketDetailsView />
1741+
</PerpsConnectionProvider>,
1742+
{
1743+
state: initialState,
1744+
},
1745+
);
1746+
1747+
// Assert - component renders with position data
1748+
expect(
1749+
getByTestId(PerpsMarketDetailsViewSelectorsIDs.CONTAINER),
1750+
).toBeTruthy();
1751+
});
1752+
1753+
it('handles undefined positionOpenedTimestamp from hook', () => {
1754+
// Arrange - Hook returns undefined timestamp (e.g., new position)
1755+
mockUseHasExistingPosition.mockReturnValue({
1756+
hasPosition: true,
17621757
isLoading: false,
17631758
error: null,
1764-
refresh: jest.fn(),
1765-
isRefreshing: false,
1759+
existingPosition: {
1760+
symbol: 'BTC',
1761+
size: '0.5',
1762+
entryPrice: '44000',
1763+
positionValue: '22000',
1764+
unrealizedPnl: '50',
1765+
marginUsed: '500',
1766+
leverage: { type: 'isolated', value: 5 },
1767+
liquidationPrice: '40000',
1768+
maxLeverage: 20,
1769+
returnOnEquity: '1.14',
1770+
cumulativeFunding: {
1771+
allTime: '0',
1772+
sinceOpen: '0',
1773+
sinceChange: '0',
1774+
},
1775+
},
1776+
refreshPosition: jest.fn(),
1777+
positionOpenedTimestamp: undefined, // No timestamp available yet
17661778
});
17671779

17681780
// Act
@@ -1775,11 +1787,10 @@ describe('PerpsMarketDetailsView', () => {
17751787
},
17761788
);
17771789

1778-
// Assert
1790+
// Assert - component still renders correctly
17791791
expect(
17801792
getByTestId(PerpsMarketDetailsViewSelectorsIDs.CONTAINER),
17811793
).toBeTruthy();
1782-
expect(mockUsePerpsOrderFillsImpl).toHaveBeenCalled();
17831794
});
17841795
});
17851796

0 commit comments

Comments
 (0)