mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Move all the infrastructure to test nightlies out of facebook/react-native (#53280)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53280 I've moved a lot of the nightly testing infrastructure on a RNC repo here: https://github.com/react-native-community/nightly-tests/ This allows us to iterate faster without having to wait for diffs to be imported and test inside fbsource. Changelog: [Internal] [Changed] - Reviewed By: cipolleschi Differential Revision: D80262856 fbshipit-source-id: dc2dfe75901ac78ec9f6e940540102276d34acdf
This commit is contained in:
committed by
Facebook GitHub Bot
parent
9ca43e4095
commit
f502cae9b5
@@ -1,895 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
FirebaseClient,
|
||||
compareResults,
|
||||
getYesterdayDate,
|
||||
getTodayDate,
|
||||
} = require('../firebaseUtils');
|
||||
|
||||
describe('FirebaseClient', () => {
|
||||
const originalFetch = global.fetch;
|
||||
const originalEnv = process.env;
|
||||
|
||||
beforeEach(() => {
|
||||
global.fetch = jest.fn();
|
||||
process.env = {
|
||||
...originalEnv,
|
||||
FIREBASE_APP_EMAIL: 'test@example.com',
|
||||
FIREBASE_APP_PASS: 'testpassword',
|
||||
FIREBASE_APP_APIKEY: 'test-api-key',
|
||||
FIREBASE_APP_PROJECTNAME: 'test-project',
|
||||
};
|
||||
jest.spyOn(console, 'log').mockImplementation(() => {});
|
||||
jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
global.fetch = originalFetch;
|
||||
process.env = originalEnv;
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with environment variables', () => {
|
||||
const client = new FirebaseClient();
|
||||
expect(client.email).toBe('test@example.com');
|
||||
expect(client.password).toBe('testpassword');
|
||||
expect(client.apiKey).toBe('test-api-key');
|
||||
expect(client.projectId).toBe('test-project');
|
||||
expect(client.databaseUrl).toBe('test-project.firebaseio.com');
|
||||
expect(client.idToken).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('authenticate', () => {
|
||||
it('should authenticate successfully', async () => {
|
||||
const mockResponse = {
|
||||
idToken: 'mock-id-token',
|
||||
refreshToken: 'mock-refresh-token',
|
||||
};
|
||||
|
||||
global.fetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResponse)),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
await client.authenticate();
|
||||
|
||||
expect(client.idToken).toBe('mock-id-token');
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
'https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=test-api-key',
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: 'test@example.com',
|
||||
password: 'testpassword',
|
||||
returnSecureToken: true,
|
||||
}),
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error when email is missing', async () => {
|
||||
delete process.env.FIREBASE_APP_EMAIL;
|
||||
const client = new FirebaseClient();
|
||||
|
||||
await expect(client.authenticate()).rejects.toThrow(
|
||||
'Firebase credentials not found in environment variables',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error when password is missing', async () => {
|
||||
delete process.env.FIREBASE_APP_PASS;
|
||||
const client = new FirebaseClient();
|
||||
|
||||
await expect(client.authenticate()).rejects.toThrow(
|
||||
'Firebase credentials not found in environment variables',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle authentication failure', async () => {
|
||||
global.fetch.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
status: 400,
|
||||
text: jest.fn().mockResolvedValueOnce(
|
||||
JSON.stringify({
|
||||
error: {message: 'Invalid credentials'},
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
|
||||
await expect(client.authenticate()).rejects.toThrow(
|
||||
'HTTP 400: Invalid credentials',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('makeRequest', () => {
|
||||
it('should make successful GET request', async () => {
|
||||
const mockData = {test: 'data'};
|
||||
global.fetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockData)),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
const result = await client.makeRequest('example.com', '/test', 'GET');
|
||||
|
||||
expect(result).toEqual(mockData);
|
||||
expect(global.fetch).toHaveBeenCalledWith('https://example.com/test', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should make successful POST request with data', async () => {
|
||||
const mockData = {success: true};
|
||||
const postData = {test: 'post data'};
|
||||
|
||||
global.fetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockData)),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
const result = await client.makeRequest(
|
||||
'example.com',
|
||||
'/test',
|
||||
'POST',
|
||||
postData,
|
||||
);
|
||||
|
||||
expect(result).toEqual(mockData);
|
||||
expect(global.fetch).toHaveBeenCalledWith('https://example.com/test', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(postData),
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle non-JSON response', async () => {
|
||||
const textResponse = 'plain text response';
|
||||
global.fetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce(textResponse),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
const result = await client.makeRequest('example.com', '/test', 'GET');
|
||||
|
||||
expect(result).toBe(textResponse);
|
||||
});
|
||||
|
||||
it('should handle HTTP error with JSON error message', async () => {
|
||||
global.fetch.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
status: 404,
|
||||
text: jest.fn().mockResolvedValueOnce(
|
||||
JSON.stringify({
|
||||
error: {message: 'Not found'},
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
|
||||
await expect(
|
||||
client.makeRequest('example.com', '/test', 'GET'),
|
||||
).rejects.toThrow('HTTP 404: Not found');
|
||||
});
|
||||
|
||||
it('should handle HTTP error with plain text error message', async () => {
|
||||
global.fetch.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
status: 500,
|
||||
text: jest.fn().mockResolvedValueOnce('Internal Server Error'),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
|
||||
await expect(
|
||||
client.makeRequest('example.com', '/test', 'GET'),
|
||||
).rejects.toThrow('HTTP 500: Internal Server Error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('makeDatabaseRequest', () => {
|
||||
it('should make database request with existing token', async () => {
|
||||
const mockData = {test: 'data'};
|
||||
global.fetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockData)),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
client.idToken = 'existing-token';
|
||||
|
||||
const result = await client.makeDatabaseRequest('2023-12-01', 'GET');
|
||||
|
||||
expect(result).toEqual(mockData);
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
'https://test-project.firebaseio.com/nightly-results/2023-12-01.json?auth=existing-token',
|
||||
{
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should authenticate before making request if no token exists', async () => {
|
||||
const authResponse = {idToken: 'new-token'};
|
||||
const dataResponse = {test: 'data'};
|
||||
|
||||
global.fetch
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce(JSON.stringify(authResponse)),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce(JSON.stringify(dataResponse)),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
const result = await client.makeDatabaseRequest('2023-12-01', 'GET');
|
||||
|
||||
expect(result).toEqual(dataResponse);
|
||||
expect(global.fetch).toHaveBeenCalledTimes(2);
|
||||
expect(client.idToken).toBe('new-token');
|
||||
});
|
||||
|
||||
it('should make PUT request with data', async () => {
|
||||
global.fetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce('null'),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
client.idToken = 'existing-token';
|
||||
const testData = [{library: 'test', status: 'success'}];
|
||||
|
||||
await client.makeDatabaseRequest('2023-12-01', 'PUT', testData);
|
||||
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
'https://test-project.firebaseio.com/nightly-results/2023-12-01.json?auth=existing-token',
|
||||
{
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(testData),
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('storeResults', () => {
|
||||
it('should store results successfully', async () => {
|
||||
global.fetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce('null'),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
client.idToken = 'existing-token';
|
||||
const results = [{library: 'test', status: 'success'}];
|
||||
|
||||
await client.storeResults('2023-12-01', results);
|
||||
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
'https://test-project.firebaseio.com/nightly-results/2023-12-01.json?auth=existing-token',
|
||||
{
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(results),
|
||||
},
|
||||
);
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'Successfully stored results for 2023-12-01',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getResults', () => {
|
||||
it('should retrieve results successfully', async () => {
|
||||
const mockResults = [{library: 'test', status: 'success'}];
|
||||
global.fetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
client.idToken = 'existing-token';
|
||||
|
||||
const results = await client.getResults('2023-12-01');
|
||||
|
||||
expect(results).toEqual(mockResults);
|
||||
});
|
||||
|
||||
it('should return null for 404 errors', async () => {
|
||||
global.fetch.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
status: 404,
|
||||
text: jest.fn().mockResolvedValueOnce('Not Found'),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
client.idToken = 'existing-token';
|
||||
|
||||
const results = await client.getResults('2023-12-01');
|
||||
|
||||
expect(results).toBeNull();
|
||||
});
|
||||
|
||||
it('should throw error for non-404 HTTP errors', async () => {
|
||||
global.fetch.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
status: 500,
|
||||
text: jest.fn().mockResolvedValueOnce('Internal Server Error'),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
client.idToken = 'existing-token';
|
||||
|
||||
await expect(client.getResults('2023-12-01')).rejects.toThrow(
|
||||
'HTTP 500: Internal Server Error',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLatestResults', () => {
|
||||
it('should authenticate before making requests if no token exists', async () => {
|
||||
const authResponse = {idToken: 'new-token'};
|
||||
const mockResults = [{library: 'test', status: 'success'}];
|
||||
|
||||
global.fetch
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce(JSON.stringify(authResponse)),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
const result = await client.getLatestResults('2023-12-15', 1);
|
||||
|
||||
expect(result).toEqual({
|
||||
results: mockResults,
|
||||
date: '2023-12-14',
|
||||
});
|
||||
expect(client.idToken).toBe('new-token');
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'Checking for results on 2023-12-14 (1 days back)...',
|
||||
);
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'Found results from 2023-12-14 (1 days back)',
|
||||
);
|
||||
});
|
||||
|
||||
it('should find results from the previous day', async () => {
|
||||
const mockResults = [{library: 'test', status: 'success'}];
|
||||
global.fetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
client.idToken = 'existing-token';
|
||||
|
||||
const result = await client.getLatestResults('2023-12-15', 7);
|
||||
|
||||
expect(result).toEqual({
|
||||
results: mockResults,
|
||||
date: '2023-12-14',
|
||||
});
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'Checking for results on 2023-12-14 (1 days back)...',
|
||||
);
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'Found results from 2023-12-14 (1 days back)',
|
||||
);
|
||||
});
|
||||
|
||||
it('should find results from several days back', async () => {
|
||||
const mockResults = [{library: 'test', status: 'success'}];
|
||||
|
||||
// Mock 404 responses for first 2 days, then success on 3rd day
|
||||
global.fetch
|
||||
.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
status: 404,
|
||||
text: jest.fn().mockResolvedValueOnce('Not Found'),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
status: 404,
|
||||
text: jest.fn().mockResolvedValueOnce('Not Found'),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
client.idToken = 'existing-token';
|
||||
|
||||
const result = await client.getLatestResults('2023-12-15', 7);
|
||||
|
||||
expect(result).toEqual({
|
||||
results: mockResults,
|
||||
date: '2023-12-12',
|
||||
});
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'Checking for results on 2023-12-14 (1 days back)...',
|
||||
);
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'Checking for results on 2023-12-13 (2 days back)...',
|
||||
);
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'Checking for results on 2023-12-12 (3 days back)...',
|
||||
);
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'Found results from 2023-12-12 (3 days back)',
|
||||
);
|
||||
});
|
||||
|
||||
it('should skip empty results and continue searching', async () => {
|
||||
const mockResults = [{library: 'test', status: 'success'}];
|
||||
|
||||
// Mock empty array for first day, then valid results on second day
|
||||
global.fetch
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce(JSON.stringify([])),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
client.idToken = 'existing-token';
|
||||
|
||||
const result = await client.getLatestResults('2023-12-15', 7);
|
||||
|
||||
expect(result).toEqual({
|
||||
results: mockResults,
|
||||
date: '2023-12-13',
|
||||
});
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'Checking for results on 2023-12-14 (1 days back)...',
|
||||
);
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'Checking for results on 2023-12-13 (2 days back)...',
|
||||
);
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'Found results from 2023-12-13 (2 days back)',
|
||||
);
|
||||
});
|
||||
|
||||
it('should return null when no results found within maxDaysBack', async () => {
|
||||
// Mock 404 responses for all days
|
||||
global.fetch.mockResolvedValue({
|
||||
ok: false,
|
||||
status: 404,
|
||||
text: jest.fn().mockResolvedValue('Not Found'),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
client.idToken = 'existing-token';
|
||||
|
||||
const result = await client.getLatestResults('2023-12-15', 3);
|
||||
|
||||
expect(result).toEqual({
|
||||
results: null,
|
||||
date: null,
|
||||
});
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'No previous results found within the last 3 days',
|
||||
);
|
||||
expect(global.fetch).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it('should use default maxDaysBack of 7 when not specified', async () => {
|
||||
// Mock 404 responses for all days
|
||||
global.fetch.mockResolvedValue({
|
||||
ok: false,
|
||||
status: 404,
|
||||
text: jest.fn().mockResolvedValue('Not Found'),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
client.idToken = 'existing-token';
|
||||
|
||||
const result = await client.getLatestResults('2023-12-15');
|
||||
|
||||
expect(result).toEqual({
|
||||
results: null,
|
||||
date: null,
|
||||
});
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'No previous results found within the last 7 days',
|
||||
);
|
||||
expect(global.fetch).toHaveBeenCalledTimes(7);
|
||||
});
|
||||
|
||||
it('should handle non-404 errors and continue searching', async () => {
|
||||
const mockResults = [{library: 'test', status: 'success'}];
|
||||
|
||||
// Mock 500 error for first day, then success on second day
|
||||
global.fetch
|
||||
.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
status: 500,
|
||||
text: jest.fn().mockResolvedValueOnce('Internal Server Error'),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
client.idToken = 'existing-token';
|
||||
|
||||
const result = await client.getLatestResults('2023-12-15', 7);
|
||||
|
||||
expect(result).toEqual({
|
||||
results: mockResults,
|
||||
date: '2023-12-13',
|
||||
});
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'No results found for 2023-12-14: HTTP 500: Internal Server Error',
|
||||
);
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'Found results from 2023-12-13 (2 days back)',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle date boundaries correctly', async () => {
|
||||
const mockResults = [{library: 'test', status: 'success'}];
|
||||
global.fetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
client.idToken = 'existing-token';
|
||||
|
||||
// Test month boundary
|
||||
const result = await client.getLatestResults('2023-12-01', 1);
|
||||
|
||||
expect(result).toEqual({
|
||||
results: mockResults,
|
||||
date: '2023-11-30',
|
||||
});
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'Checking for results on 2023-11-30 (1 days back)...',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle year boundary correctly', async () => {
|
||||
const mockResults = [{library: 'test', status: 'success'}];
|
||||
global.fetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
client.idToken = 'existing-token';
|
||||
|
||||
// Test year boundary
|
||||
const result = await client.getLatestResults('2024-01-01', 1);
|
||||
|
||||
expect(result).toEqual({
|
||||
results: mockResults,
|
||||
date: '2023-12-31',
|
||||
});
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'Checking for results on 2023-12-31 (1 days back)...',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle null results and continue searching', async () => {
|
||||
const mockResults = [{library: 'test', status: 'success'}];
|
||||
|
||||
// Mock null for first day, then valid results on second day
|
||||
global.fetch
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce('null'),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
client.idToken = 'existing-token';
|
||||
|
||||
const result = await client.getLatestResults('2023-12-15', 7);
|
||||
|
||||
expect(result).toEqual({
|
||||
results: mockResults,
|
||||
date: '2023-12-13',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('compareResults', () => {
|
||||
it('should handle null previous results', () => {
|
||||
const currentResults = [
|
||||
{library: 'lib1', platform: 'iOS', status: 'failed'},
|
||||
{library: 'lib2', platform: 'Android', status: 'success'},
|
||||
];
|
||||
|
||||
const result = compareResults(currentResults, null);
|
||||
|
||||
expect(result).toEqual({
|
||||
broken: [],
|
||||
recovered: [],
|
||||
newFailures: [{library: 'lib1', platform: 'iOS', status: 'failed'}],
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle undefined previous results', () => {
|
||||
const currentResults = [
|
||||
{library: 'lib1', platform: 'iOS', status: 'failed'},
|
||||
];
|
||||
|
||||
const result = compareResults(currentResults, undefined);
|
||||
|
||||
expect(result).toEqual({
|
||||
broken: [],
|
||||
recovered: [],
|
||||
newFailures: [{library: 'lib1', platform: 'iOS', status: 'failed'}],
|
||||
});
|
||||
});
|
||||
|
||||
it('should identify broken tests', () => {
|
||||
const currentResults = [
|
||||
{library: 'lib1', platform: 'iOS', status: 'failed'},
|
||||
{library: 'lib2', platform: 'Android', status: 'success'},
|
||||
];
|
||||
|
||||
const previousResults = [
|
||||
{library: 'lib1', platform: 'iOS', status: 'success'},
|
||||
{library: 'lib2', platform: 'Android', status: 'success'},
|
||||
];
|
||||
|
||||
const result = compareResults(currentResults, previousResults);
|
||||
|
||||
expect(result.broken).toEqual([
|
||||
{
|
||||
library: 'lib1',
|
||||
platform: 'iOS',
|
||||
previousStatus: 'success',
|
||||
currentStatus: 'failed',
|
||||
},
|
||||
]);
|
||||
expect(result.recovered).toEqual([]);
|
||||
});
|
||||
|
||||
it('should identify recovered tests', () => {
|
||||
const currentResults = [
|
||||
{library: 'lib1', platform: 'iOS', status: 'success'},
|
||||
{library: 'lib2', platform: 'Android', status: 'success'},
|
||||
];
|
||||
|
||||
const previousResults = [
|
||||
{library: 'lib1', platform: 'iOS', status: 'failed'},
|
||||
{library: 'lib2', platform: 'Android', status: 'success'},
|
||||
];
|
||||
|
||||
const result = compareResults(currentResults, previousResults);
|
||||
|
||||
expect(result.broken).toEqual([]);
|
||||
expect(result.recovered).toEqual([
|
||||
{
|
||||
library: 'lib1',
|
||||
platform: 'iOS',
|
||||
previousStatus: 'failed',
|
||||
currentStatus: 'success',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should identify both broken and recovered tests', () => {
|
||||
const currentResults = [
|
||||
{library: 'lib1', platform: 'iOS', status: 'failed'},
|
||||
{library: 'lib2', platform: 'Android', status: 'success'},
|
||||
{library: 'lib3', platform: 'iOS', status: 'success'},
|
||||
];
|
||||
|
||||
const previousResults = [
|
||||
{library: 'lib1', platform: 'iOS', status: 'success'},
|
||||
{library: 'lib2', platform: 'Android', status: 'failed'},
|
||||
{library: 'lib3', platform: 'iOS', status: 'success'},
|
||||
];
|
||||
|
||||
const result = compareResults(currentResults, previousResults);
|
||||
|
||||
expect(result.broken).toEqual([
|
||||
{
|
||||
library: 'lib1',
|
||||
platform: 'iOS',
|
||||
previousStatus: 'success',
|
||||
currentStatus: 'failed',
|
||||
},
|
||||
]);
|
||||
expect(result.recovered).toEqual([
|
||||
{
|
||||
library: 'lib2',
|
||||
platform: 'Android',
|
||||
previousStatus: 'failed',
|
||||
currentStatus: 'success',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle tests that are not in previous results', () => {
|
||||
const currentResults = [
|
||||
{library: 'lib1', platform: 'iOS', status: 'failed'},
|
||||
{library: 'lib2', platform: 'Android', status: 'success'},
|
||||
];
|
||||
|
||||
const previousResults = [
|
||||
{library: 'lib1', platform: 'iOS', status: 'success'},
|
||||
];
|
||||
|
||||
const result = compareResults(currentResults, previousResults);
|
||||
|
||||
expect(result.broken).toEqual([
|
||||
{
|
||||
library: 'lib1',
|
||||
platform: 'iOS',
|
||||
previousStatus: 'success',
|
||||
currentStatus: 'failed',
|
||||
},
|
||||
]);
|
||||
expect(result.recovered).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle empty current results', () => {
|
||||
const currentResults = [];
|
||||
const previousResults = [
|
||||
{library: 'lib1', platform: 'iOS', status: 'success'},
|
||||
];
|
||||
|
||||
const result = compareResults(currentResults, previousResults);
|
||||
|
||||
expect(result.broken).toEqual([]);
|
||||
expect(result.recovered).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle empty previous results', () => {
|
||||
const currentResults = [
|
||||
{library: 'lib1', platform: 'iOS', status: 'failed'},
|
||||
];
|
||||
const previousResults = [];
|
||||
|
||||
const result = compareResults(currentResults, previousResults);
|
||||
|
||||
expect(result.broken).toEqual([]);
|
||||
expect(result.recovered).toEqual([]);
|
||||
// When previousResults is an empty array (not null/undefined),
|
||||
// the function doesn't return newFailures property
|
||||
expect(result.newFailures).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should handle different status values', () => {
|
||||
const currentResults = [
|
||||
{library: 'lib1', platform: 'iOS', status: 'timeout'},
|
||||
{library: 'lib2', platform: 'Android', status: 'success'},
|
||||
];
|
||||
|
||||
const previousResults = [
|
||||
{library: 'lib1', platform: 'iOS', status: 'success'},
|
||||
{library: 'lib2', platform: 'Android', status: 'error'},
|
||||
];
|
||||
|
||||
const result = compareResults(currentResults, previousResults);
|
||||
|
||||
expect(result.broken).toEqual([
|
||||
{
|
||||
library: 'lib1',
|
||||
platform: 'iOS',
|
||||
previousStatus: 'success',
|
||||
currentStatus: 'timeout',
|
||||
},
|
||||
]);
|
||||
expect(result.recovered).toEqual([
|
||||
{
|
||||
library: 'lib2',
|
||||
platform: 'Android',
|
||||
previousStatus: 'error',
|
||||
currentStatus: 'success',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getYesterdayDate', () => {
|
||||
it("should return yesterday's date in YYYY-MM-DD format", () => {
|
||||
const mockDate = new Date('2023-12-15T10:30:00Z');
|
||||
jest.spyOn(global, 'Date').mockImplementation(() => mockDate);
|
||||
|
||||
const result = getYesterdayDate();
|
||||
|
||||
expect(result).toBe('2023-12-14');
|
||||
|
||||
global.Date.mockRestore();
|
||||
});
|
||||
|
||||
it('should handle month boundary correctly', () => {
|
||||
const mockDate = new Date('2023-12-01T10:30:00Z');
|
||||
jest.spyOn(global, 'Date').mockImplementation(() => mockDate);
|
||||
|
||||
const result = getYesterdayDate();
|
||||
|
||||
expect(result).toBe('2023-11-30');
|
||||
|
||||
global.Date.mockRestore();
|
||||
});
|
||||
|
||||
it('should handle year boundary correctly', () => {
|
||||
const mockDate = new Date('2024-01-01T10:30:00Z');
|
||||
jest.spyOn(global, 'Date').mockImplementation(() => mockDate);
|
||||
|
||||
const result = getYesterdayDate();
|
||||
|
||||
expect(result).toBe('2023-12-31');
|
||||
|
||||
global.Date.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTodayDate', () => {
|
||||
it("should return today's date in YYYY-MM-DD format", () => {
|
||||
const mockDate = new Date('2023-12-15T10:30:00Z');
|
||||
jest.spyOn(global, 'Date').mockImplementation(() => mockDate);
|
||||
|
||||
const result = getTodayDate();
|
||||
|
||||
expect(result).toBe('2023-12-15');
|
||||
|
||||
global.Date.mockRestore();
|
||||
});
|
||||
|
||||
it('should handle different times of day correctly', () => {
|
||||
const mockDate = new Date('2023-12-15T23:59:59Z');
|
||||
jest.spyOn(global, 'Date').mockImplementation(() => mockDate);
|
||||
|
||||
const result = getTodayDate();
|
||||
|
||||
expect(result).toBe('2023-12-15');
|
||||
|
||||
global.Date.mockRestore();
|
||||
});
|
||||
});
|
||||
@@ -1,189 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
prepareFailurePayload,
|
||||
sendMessageToDiscord,
|
||||
} = require('../notifyDiscord');
|
||||
|
||||
describe('prepareFailurePayload', () => {
|
||||
it('should handle undefined failures', () => {
|
||||
const message = prepareFailurePayload(undefined);
|
||||
expect(message).toEqual({
|
||||
content:
|
||||
'⚠️ **React Native Nightly Integration Failures** ⚠️\n\nNo failures to report.',
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle empty failures array', () => {
|
||||
const message = prepareFailurePayload([]);
|
||||
expect(message).toEqual({
|
||||
content:
|
||||
'⚠️ **React Native Nightly Integration Failures** ⚠️\n\nNo failures to report.',
|
||||
});
|
||||
});
|
||||
|
||||
it('should format a single failure correctly', () => {
|
||||
const failures = [
|
||||
{
|
||||
library: 'react-native-reanimated',
|
||||
platform: 'iOS',
|
||||
},
|
||||
];
|
||||
|
||||
const message = prepareFailurePayload(failures);
|
||||
expect(message).toEqual({
|
||||
content:
|
||||
'⚠️ **React Native Nightly Integration Failures** ⚠️\n\nThe integration of libraries with React Native nightly failed for the following libraries:\n\n❌ [iOS] react-native-reanimated',
|
||||
});
|
||||
});
|
||||
|
||||
it('should sort multiple failures by platform and library name', () => {
|
||||
const failures = [
|
||||
{
|
||||
library: 'react-native-reanimated',
|
||||
platform: 'iOS',
|
||||
},
|
||||
{
|
||||
library: 'react-native-gesture-handler',
|
||||
platform: 'Android',
|
||||
},
|
||||
{
|
||||
library: 'react-native-screens',
|
||||
platform: 'iOS',
|
||||
},
|
||||
{
|
||||
library: 'react-native-svg',
|
||||
platform: 'Android',
|
||||
},
|
||||
];
|
||||
|
||||
const message = prepareFailurePayload(failures);
|
||||
|
||||
// The failures should be sorted: first Android (alphabetically), then iOS
|
||||
// Within each platform, libraries should be sorted alphabetically
|
||||
expect(message).toEqual({
|
||||
content:
|
||||
'⚠️ **React Native Nightly Integration Failures** ⚠️\n\nThe integration of libraries with React Native nightly failed for the following libraries:\n\n❌ [Android] react-native-gesture-handler\n❌ [Android] react-native-svg\n❌ [iOS] react-native-reanimated\n❌ [iOS] react-native-screens',
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle failures with missing properties', () => {
|
||||
const failures = [
|
||||
{
|
||||
// Missing library
|
||||
platform: 'iOS',
|
||||
},
|
||||
{
|
||||
library: 'react-native-gesture-handler',
|
||||
// Missing platform
|
||||
},
|
||||
{
|
||||
// Both missing
|
||||
},
|
||||
];
|
||||
|
||||
const message = prepareFailurePayload(failures);
|
||||
|
||||
expect(message).toEqual({
|
||||
content:
|
||||
'⚠️ **React Native Nightly Integration Failures** ⚠️\n\nThe integration of libraries with React Native nightly failed for the following libraries:\n\n❌ [iOS] Unknown\n❌ [Unknown] react-native-gesture-handler\n❌ [Unknown] Unknown',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('sendMessageToDiscord', () => {
|
||||
// Store the original fetch function
|
||||
const originalFetch = global.fetch;
|
||||
|
||||
// Setup and teardown for each test
|
||||
beforeEach(() => {
|
||||
// Mock the global fetch function
|
||||
global.fetch = jest.fn();
|
||||
// Silence console logs during tests
|
||||
jest.spyOn(console, 'log').mockImplementation(() => {});
|
||||
jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Restore the original fetch function
|
||||
global.fetch = originalFetch;
|
||||
// Restore console functions
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should throw an error if webhook URL is missing', async () => {
|
||||
await expect(sendMessageToDiscord(null, {})).rejects.toThrow(
|
||||
'Discord webhook URL is missing',
|
||||
);
|
||||
});
|
||||
|
||||
it('should send a message successfully', async () => {
|
||||
// Mock a successful response
|
||||
global.fetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
status: 200,
|
||||
});
|
||||
|
||||
const webhook = 'https://discord.com/api/webhooks/123/abc';
|
||||
const message = {content: 'Test message'};
|
||||
|
||||
await expect(sendMessageToDiscord(webhook, message)).resolves.not.toThrow();
|
||||
|
||||
// Verify fetch was called with the right arguments
|
||||
expect(global.fetch).toHaveBeenCalledWith(webhook, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(message),
|
||||
});
|
||||
|
||||
// Verify console.log was called
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'Successfully sent message to Discord',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error if the response is not ok', async () => {
|
||||
// Mock a failed response
|
||||
global.fetch.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
status: 400,
|
||||
text: jest.fn().mockResolvedValueOnce('Bad Request'),
|
||||
});
|
||||
|
||||
const webhook = 'https://discord.com/api/webhooks/123/abc';
|
||||
const message = {content: 'Test message'};
|
||||
|
||||
await expect(sendMessageToDiscord(webhook, message)).rejects.toThrow(
|
||||
'HTTP status code: 400',
|
||||
);
|
||||
|
||||
// Verify console.error was called
|
||||
expect(console.error).toHaveBeenCalledWith(
|
||||
'Failed to send message to Discord: 400 Bad Request',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error if fetch fails', async () => {
|
||||
// Mock a network error
|
||||
const networkError = new Error('Network error');
|
||||
global.fetch.mockRejectedValueOnce(networkError);
|
||||
|
||||
const webhook = 'https://discord.com/api/webhooks/123/abc';
|
||||
const message = {content: 'Test message'};
|
||||
|
||||
await expect(sendMessageToDiscord(webhook, message)).rejects.toThrow(
|
||||
'Network error',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,174 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const {
|
||||
prepareFailurePayload,
|
||||
prepareComparisonPayload,
|
||||
sendMessageToDiscord,
|
||||
} = require('./notifyDiscord');
|
||||
const {
|
||||
FirebaseClient,
|
||||
compareResults,
|
||||
getYesterdayDate,
|
||||
getTodayDate,
|
||||
} = require('./firebaseUtils');
|
||||
|
||||
function readOutcomes() {
|
||||
const baseDir = '/tmp';
|
||||
let outcomes = [];
|
||||
fs.readdirSync(baseDir).forEach(file => {
|
||||
const fullPath = path.join(baseDir, file);
|
||||
if (fullPath.endsWith('outcome') && fs.statSync(fullPath).isDirectory) {
|
||||
fs.readdirSync(fullPath).forEach(subFile => {
|
||||
const subFullPath = path.join(fullPath, subFile);
|
||||
if (subFullPath.endsWith('outcome')) {
|
||||
const [library, status] = String(fs.readFileSync(subFullPath, 'utf8'))
|
||||
.trim()
|
||||
.split(':');
|
||||
const platform = subFile.includes('android') ? 'Android' : 'iOS';
|
||||
console.log(
|
||||
`[${platform}] ${library} completed with status ${status}`,
|
||||
);
|
||||
outcomes.push({
|
||||
library: library.trim(),
|
||||
platform,
|
||||
status: status.trim(),
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if (fullPath.endsWith('outcome')) {
|
||||
const [library, status] = String(fs.readFileSync(fullPath, 'utf8'))
|
||||
.trim()
|
||||
.split(':');
|
||||
const platform = file.includes('android') ? 'Android' : 'iOS';
|
||||
console.log(`[${platform}] ${library} completed with status ${status}`);
|
||||
outcomes.push({
|
||||
library: library.trim(),
|
||||
platform,
|
||||
status: status.trim(),
|
||||
});
|
||||
}
|
||||
});
|
||||
return outcomes;
|
||||
}
|
||||
|
||||
function printFailures(outcomes) {
|
||||
console.log('Printing failures...');
|
||||
let failedLibraries = [];
|
||||
outcomes.forEach(entry => {
|
||||
if (entry.status !== 'success') {
|
||||
console.log(
|
||||
`❌ [${entry.platform}] ${entry.library} failed with status ${entry.status}`,
|
||||
);
|
||||
failedLibraries.push({
|
||||
library: entry.library,
|
||||
platform: entry.platform,
|
||||
});
|
||||
}
|
||||
});
|
||||
return failedLibraries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to Discord with the list of failures.
|
||||
* @param {string} webHook - The Discord webhook URL
|
||||
* @param {Array<Object>} failures - List of failures to report
|
||||
* @returns {Promise<void>} - A promise that resolves when the message is sent
|
||||
*/
|
||||
async function notifyDiscord(webHook, failures) {
|
||||
if (!webHook) {
|
||||
console.error('Discord webhook URL is missing');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!failures || failures.length === 0) {
|
||||
console.log('No failures to report to Discord');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Use the prepareFailurePayload function to format the message
|
||||
const message = prepareFailurePayload(failures);
|
||||
|
||||
// Use the sendMessageToDiscord function to send the message
|
||||
await sendMessageToDiscord(webHook, message);
|
||||
} catch (error) {
|
||||
console.error('Error in notifyDiscord function:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function collectResults(discordWebHook) {
|
||||
const outcomes = readOutcomes();
|
||||
const failures = printFailures(outcomes);
|
||||
|
||||
// Send failure notification if there are current failures
|
||||
if (failures.length > 0) {
|
||||
if (discordWebHook) {
|
||||
console.log('Sending current failures to Discord...');
|
||||
await notifyDiscord(discordWebHook, failures);
|
||||
} else {
|
||||
console.log('Discord webhook not set');
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize Firebase client
|
||||
const firebaseClient = new FirebaseClient();
|
||||
const today = getTodayDate();
|
||||
|
||||
try {
|
||||
// Store today's results in Firebase
|
||||
console.log(`Storing results for ${today} in Firebase...`);
|
||||
await firebaseClient.storeResults(today, outcomes);
|
||||
|
||||
// Get the most recent previous results for comparison
|
||||
console.log(`Looking for most recent previous results before ${today}...`);
|
||||
const {results: previousResults, date: previousDate} =
|
||||
await firebaseClient.getLatestResults(today);
|
||||
|
||||
let broken = [];
|
||||
let recovered = [];
|
||||
|
||||
if (previousResults) {
|
||||
console.log(`Comparing with results from ${previousDate}`);
|
||||
// Compare results and identify broken/recovered jobs
|
||||
const comparison = compareResults(outcomes, previousResults);
|
||||
broken = comparison.broken;
|
||||
recovered = comparison.recovered;
|
||||
|
||||
console.log(
|
||||
`Found ${broken.length} newly broken jobs and ${recovered.length} recovered jobs compared to ${previousDate}`,
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
'No previous results found for comparison - this might be the first run or no recent data available',
|
||||
);
|
||||
}
|
||||
|
||||
// Send comparison message to Discord if there are changes
|
||||
if (discordWebHook && (broken.length > 0 || recovered.length > 0)) {
|
||||
console.log('Sending comparison results to Discord...');
|
||||
const comparisonMessage = prepareComparisonPayload(broken, recovered);
|
||||
await sendMessageToDiscord(discordWebHook, comparisonMessage);
|
||||
}
|
||||
|
||||
console.log('✅ All tests passed!');
|
||||
} catch (error) {
|
||||
console.error('Error in collectResults:', error);
|
||||
// If Firebase fails but there are no test failures, don't fail the workflow
|
||||
console.log('⚠️ Firebase operations failed, but all tests passed');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
collectResults,
|
||||
notifyDiscord,
|
||||
};
|
||||
@@ -1,136 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sends a message to Discord using the webhook URL.
|
||||
* @param {string} webHook - The Discord webhook URL
|
||||
* @param {Object} message - The message to send
|
||||
* @returns {Promise<void>} - A promise that resolves when the message is sent
|
||||
*/
|
||||
async function sendMessageToDiscord(webHook, message) {
|
||||
if (!webHook) {
|
||||
throw new Error('Discord webhook URL is missing');
|
||||
}
|
||||
|
||||
// Send the request using fetch
|
||||
const response = await fetch(webHook, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(message),
|
||||
});
|
||||
|
||||
// Handle the response
|
||||
if (response.ok) {
|
||||
console.log('Successfully sent message to Discord');
|
||||
return;
|
||||
} else {
|
||||
const errorText = await response.text();
|
||||
console.error(
|
||||
`Failed to send message to Discord: ${response.status} ${errorText}`,
|
||||
);
|
||||
throw new Error(`HTTP status code: ${response.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts jobs by platform first, then by library name.
|
||||
* @param {Array<Object>} jobs - Array of jobs with platform and library properties
|
||||
* @returns {Array<Object>} - Sorted array of jobs
|
||||
*/
|
||||
function sortResultsByPlatformAndLibrary(jobs) {
|
||||
return [...jobs].sort((a, b) => {
|
||||
// First sort by platform
|
||||
const platformA = a.platform || 'Unknown';
|
||||
const platformB = b.platform || 'Unknown';
|
||||
|
||||
if (platformA !== platformB) {
|
||||
return platformA.localeCompare(platformB);
|
||||
}
|
||||
|
||||
// Then sort by library name
|
||||
const libraryA = a.library || 'Unknown';
|
||||
const libraryB = b.library || 'Unknown';
|
||||
return libraryA.localeCompare(libraryB);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a formatted Discord message payload from a list of failures.
|
||||
* @param {Array<Object>} failures - List of failures to format
|
||||
* @returns {Object} - The formatted Discord message payload
|
||||
*/
|
||||
function prepareFailurePayload(failures) {
|
||||
if (!failures || failures.length === 0) {
|
||||
return {
|
||||
content:
|
||||
'⚠️ **React Native Nightly Integration Failures** ⚠️\n\nNo failures to report.',
|
||||
};
|
||||
}
|
||||
|
||||
// Sort failures by platform and then by library name
|
||||
const sortedFailures = sortResultsByPlatformAndLibrary(failures);
|
||||
|
||||
// Format the failures into a message
|
||||
const formattedFailures = sortedFailures
|
||||
.map(failure => {
|
||||
const library = failure.library || 'Unknown';
|
||||
const platform = failure.platform || 'Unknown';
|
||||
return `❌ [${platform}] ${library}`;
|
||||
})
|
||||
.join('\n');
|
||||
|
||||
return {
|
||||
content: `⚠️ **React Native Nightly Integration Failures** ⚠️\n\nThe integration of libraries with React Native nightly failed for the following libraries:\n\n${formattedFailures}`,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a formatted Discord message payload for broken and recovered nightly jobs.
|
||||
* @param {Array<Object>} broken - List of newly broken jobs
|
||||
* @param {Array<Object>} recovered - List of recovered jobs
|
||||
* @returns {Object} - The formatted Discord message payload
|
||||
*/
|
||||
function prepareComparisonPayload(broken, recovered) {
|
||||
let content = '📊 **React Native Nightly Integration Status Update** 📊\n\n';
|
||||
|
||||
if (broken.length === 0 && recovered.length === 0) {
|
||||
content +=
|
||||
'No changes from yesterday - all nightly jobs maintained their previous status.';
|
||||
} else {
|
||||
if (broken.length > 0) {
|
||||
content += '🔴 **Newly Broken Jobs:**\n';
|
||||
const sortedBroken = sortResultsByPlatformAndLibrary(broken);
|
||||
|
||||
sortedBroken.forEach(job => {
|
||||
content += `❌ [${job.platform}] ${job.library} (was ${job.previousStatus}, now ${job.currentStatus})\n`;
|
||||
});
|
||||
content += '\n';
|
||||
}
|
||||
|
||||
if (recovered.length > 0) {
|
||||
content += '🟢 **Recovered Jobs:**\n';
|
||||
const sortedRecovered = sortResultsByPlatformAndLibrary(recovered);
|
||||
|
||||
sortedRecovered.forEach(job => {
|
||||
content += `✅ [${job.platform}] ${job.library} (was ${job.previousStatus}, now ${job.currentStatus})\n`;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {content};
|
||||
}
|
||||
|
||||
// Export the functions using CommonJS syntax
|
||||
module.exports = {
|
||||
prepareFailurePayload,
|
||||
prepareComparisonPayload,
|
||||
sendMessageToDiscord,
|
||||
};
|
||||
Reference in New Issue
Block a user