mirror of
https://github.com/RocketChat/Rocket.Chat.git
synced 2025-12-28 06:47:25 +00:00
test(federation): improve and slim down config and scripts (#37769)
This commit is contained in:
parent
38a736f89c
commit
2527c47b0a
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
@ -656,6 +656,7 @@ jobs:
|
||||
ROCKETCHAT_IMAGE: ghcr.io/${{ needs.release-versions.outputs.lowercase-repo }}/rocket.chat:${{ needs.release-versions.outputs.gh-docker-tag }}
|
||||
ENTERPRISE_LICENSE_RC1: ${{ secrets.ENTERPRISE_LICENSE_RC1 }}
|
||||
QASE_TESTOPS_JEST_API_TOKEN: ${{ secrets.QASE_TESTOPS_JEST_API_TOKEN }}
|
||||
PR_NUMBER: ${{ github.event.number }}
|
||||
run: yarn test:integration --image "${ROCKETCHAT_IMAGE}"
|
||||
|
||||
report-coverage:
|
||||
|
||||
@ -8,6 +8,22 @@
|
||||
import server from '@rocket.chat/jest-presets/server';
|
||||
import type { Config } from 'jest';
|
||||
|
||||
function qaseRunTitle(): string {
|
||||
const title = ['Federation E2E Tests'];
|
||||
|
||||
if (process.env.PR_NUMBER) {
|
||||
title.push(`PR ${process.env.PR_NUMBER}`);
|
||||
}
|
||||
|
||||
if (process.env.GITHUB_RUN_ID) {
|
||||
title.push(`Run ${process.env.GITHUB_RUN_ID}`);
|
||||
}
|
||||
|
||||
title.push(new Date().toISOString());
|
||||
|
||||
return title.join(' - ');
|
||||
}
|
||||
|
||||
export default {
|
||||
preset: server.preset,
|
||||
transformIgnorePatterns: [
|
||||
@ -36,7 +52,10 @@ export default {
|
||||
testops: {
|
||||
api: { token: process.env.QASE_TESTOPS_JEST_API_TOKEN },
|
||||
project: 'RC',
|
||||
run: { complete: true },
|
||||
run: {
|
||||
title: qaseRunTitle(),
|
||||
complete: true,
|
||||
},
|
||||
},
|
||||
debug: true,
|
||||
},
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
"lint:fix": "eslint src --fix",
|
||||
"test": "jest",
|
||||
"test:integration": "./tests/scripts/run-integration-tests.sh",
|
||||
"testend-to-end": "IS_EE=true NODE_EXTRA_CA_CERTS=$(pwd)/docker-compose/traefik/certs/ca/rootCA.crt jest --config jest.config.federation.ts --forceExit --testTimeout=30000",
|
||||
"test:federation": "jest --config jest.config.federation.ts",
|
||||
"testunit": "jest",
|
||||
"typecheck": "tsc --noEmit --skipLibCheck"
|
||||
},
|
||||
|
||||
@ -22,7 +22,7 @@ import { SynapseClient } from '../helper/synapse-client';
|
||||
beforeAll(async () => {
|
||||
// Create admin request config for RC1
|
||||
rc1AdminRequestConfig = await getRequestConfig(
|
||||
federationConfig.rc1.apiUrl,
|
||||
federationConfig.rc1.url,
|
||||
federationConfig.rc1.adminUser,
|
||||
federationConfig.rc1.adminPassword,
|
||||
);
|
||||
|
||||
@ -30,7 +30,7 @@ import { SynapseClient } from '../helper/synapse-client';
|
||||
beforeAll(async () => {
|
||||
// Create admin request config for RC1
|
||||
rc1AdminRequestConfig = await getRequestConfig(
|
||||
federationConfig.rc1.apiUrl,
|
||||
federationConfig.rc1.url,
|
||||
federationConfig.rc1.adminUser,
|
||||
federationConfig.rc1.adminPassword,
|
||||
);
|
||||
@ -48,7 +48,7 @@ import { SynapseClient } from '../helper/synapse-client';
|
||||
|
||||
// Create user1 request config for RC1
|
||||
rc1User1RequestConfig = await getRequestConfig(
|
||||
federationConfig.rc1.apiUrl,
|
||||
federationConfig.rc1.url,
|
||||
federationConfig.rc1.additionalUser1.username,
|
||||
federationConfig.rc1.additionalUser1.password,
|
||||
);
|
||||
@ -84,7 +84,7 @@ import { SynapseClient } from '../helper/synapse-client';
|
||||
beforeAll(async () => {
|
||||
const user = { username: `user-${Date.now()}`, password: '123' };
|
||||
createdUser = await createUser(user, rc1AdminRequestConfig);
|
||||
userRequestConfig = await getRequestConfig(federationConfig.rc1.apiUrl, user.username, user.password);
|
||||
userRequestConfig = await getRequestConfig(federationConfig.rc1.url, user.username, user.password);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
@ -256,7 +256,7 @@ import { SynapseClient } from '../helper/synapse-client';
|
||||
describe('Go to the composer and use the /invite slash command to add a federated user', () => {
|
||||
it('It should not allow and show an error message', async () => {
|
||||
// Set up DDP listener to catch ephemeral messages
|
||||
const ddpListener = createDDPListener(federationConfig.rc1.apiUrl, rc1AdminRequestConfig);
|
||||
const ddpListener = createDDPListener(federationConfig.rc1.url, rc1AdminRequestConfig);
|
||||
|
||||
// Connect to DDP and subscribe to ephemeral messages
|
||||
await ddpListener.connect();
|
||||
|
||||
@ -5,31 +5,22 @@
|
||||
* Rocket.Chat instances, Matrix homeservers, and user credentials needed
|
||||
* for end-to-end federation testing.
|
||||
*/
|
||||
export interface IFederationConfig {
|
||||
rc1: {
|
||||
apiUrl: string;
|
||||
adminUser: string;
|
||||
adminPassword: string;
|
||||
adminMatrixUserId: string;
|
||||
additionalUser1: {
|
||||
username: string;
|
||||
password: string;
|
||||
matrixUserId: string;
|
||||
};
|
||||
};
|
||||
hs1: {
|
||||
url: string;
|
||||
adminMatrixUserId: string;
|
||||
|
||||
type FederationServerConfig = {
|
||||
url: string;
|
||||
domain: string;
|
||||
adminUser: string;
|
||||
adminPassword: string;
|
||||
adminMatrixUserId: string;
|
||||
additionalUser1: {
|
||||
username: string;
|
||||
password: string;
|
||||
homeserver: string;
|
||||
adminUser: string;
|
||||
adminPassword: string;
|
||||
additionalUser1: {
|
||||
username: string;
|
||||
password: string;
|
||||
matrixUserId: string;
|
||||
};
|
||||
matrixUserId: string;
|
||||
};
|
||||
};
|
||||
export interface IFederationConfig {
|
||||
rc1: FederationServerConfig;
|
||||
hs1: FederationServerConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -40,13 +31,12 @@ export interface IFederationConfig {
|
||||
* Throws an error if a required variable is missing or empty.
|
||||
*
|
||||
* @param name - The name of the environment variable for error messages
|
||||
* @param value - The environment variable value (may be undefined)
|
||||
* @param defaultValue - Optional default value to use if variable is not set
|
||||
* @returns The validated value (either the env var or default)
|
||||
* @throws Error if the variable is required but missing or empty
|
||||
*/
|
||||
function validateEnvVar(name: string, value: string | undefined, defaultValue?: string): string {
|
||||
const finalValue = value || defaultValue;
|
||||
function validateEnvVar(name: string, defaultValue?: string): string {
|
||||
const finalValue = process.env[name] || defaultValue;
|
||||
if (!finalValue || finalValue.trim() === '') {
|
||||
throw new Error(`Required environment variable ${name} is not set or is empty`);
|
||||
}
|
||||
@ -65,45 +55,41 @@ function validateEnvVar(name: string, value: string | undefined, defaultValue?:
|
||||
* @throws Error if any required configuration is missing or invalid
|
||||
*/
|
||||
function getFederationConfig(): IFederationConfig {
|
||||
const rcDomain = validateEnvVar('FEDERATION_RC1_DOMAIN', 'rc1');
|
||||
const rcAdminUser = validateEnvVar('FEDERATION_RC1_ADMIN_USER', 'admin');
|
||||
const rcAdminPassword = validateEnvVar('FEDERATION_RC1_ADMIN_PASSWORD', 'admin');
|
||||
const rcAdditionalUser1 = validateEnvVar('FEDERATION_RC1_ADDITIONAL_USER1', 'user2');
|
||||
const rcAdditionalUser1Password = validateEnvVar('FEDERATION_RC1_ADDITIONAL_USER1_PASSWORD', 'user2pass');
|
||||
|
||||
const hs1Domain = validateEnvVar('FEDERATION_SYNAPSE_DOMAIN', 'hs1');
|
||||
const hs1AdminUser = validateEnvVar('FEDERATION_SYNAPSE_ADMIN_USER', 'admin');
|
||||
const hs1AdminPassword = validateEnvVar('FEDERATION_SYNAPSE_ADMIN_PASSWORD', 'admin');
|
||||
const hs1AdditionalUser1 = validateEnvVar('FEDERATION_SYNAPSE_ADDITIONAL_USER1', 'alice');
|
||||
const hs1AdditionalUser1Password = validateEnvVar('FEDERATION_SYNAPSE_ADDITIONAL_USER1_PASSWORD', 'alice');
|
||||
|
||||
return {
|
||||
rc1: {
|
||||
apiUrl: validateEnvVar('FEDERATION_RC1_API_URL', process.env.FEDERATION_RC1_API_URL, 'https://rc1'),
|
||||
adminUser: validateEnvVar('FEDERATION_RC1_ADMIN_USER', process.env.FEDERATION_RC1_ADMIN_USER, 'admin'),
|
||||
adminPassword: validateEnvVar('FEDERATION_RC1_ADMIN_PASSWORD', process.env.FEDERATION_RC1_ADMIN_PASSWORD, 'admin'),
|
||||
adminMatrixUserId: validateEnvVar('FEDERATION_RC1_USER_ID', process.env.FEDERATION_RC1_USER_ID, '@admin:rc1'),
|
||||
url: `https://${rcDomain}`,
|
||||
domain: rcDomain,
|
||||
adminUser: rcAdminUser,
|
||||
adminPassword: rcAdminPassword,
|
||||
adminMatrixUserId: `@${rcAdminUser}:${rcDomain}`,
|
||||
additionalUser1: {
|
||||
username: validateEnvVar('FEDERATION_RC1_ADDITIONAL_USER1', process.env.FEDERATION_RC1_ADDITIONAL_USER1, 'user2'),
|
||||
password: validateEnvVar(
|
||||
'FEDERATION_RC1_ADDITIONAL_USER1_PASSWORD',
|
||||
process.env.FEDERATION_RC1_ADDITIONAL_USER1_PASSWORD,
|
||||
'user2pass',
|
||||
),
|
||||
matrixUserId: validateEnvVar(
|
||||
'FEDERATION_RC1_ADDITIONAL_USER1_MATRIX_ID',
|
||||
process.env.FEDERATION_RC1_ADDITIONAL_USER1_MATRIX_ID,
|
||||
'@user2:rc1',
|
||||
),
|
||||
username: rcAdditionalUser1,
|
||||
password: rcAdditionalUser1Password,
|
||||
matrixUserId: `@${rcAdditionalUser1}:${rcDomain}`,
|
||||
},
|
||||
},
|
||||
hs1: {
|
||||
url: validateEnvVar('FEDERATION_SYNAPSE_URL', process.env.FEDERATION_SYNAPSE_URL, 'https://hs1'),
|
||||
adminMatrixUserId: validateEnvVar('FEDERATION_SYNAPSE_USER', process.env.FEDERATION_SYNAPSE_USER, '@admin:hs1'),
|
||||
password: validateEnvVar('FEDERATION_SYNAPSE_PASSWORD', process.env.FEDERATION_SYNAPSE_PASSWORD, 'admin'),
|
||||
homeserver: validateEnvVar('FEDERATION_SYNAPSE_HOMESERVER', process.env.FEDERATION_SYNAPSE_HOMESERVER, 'hs1'),
|
||||
adminUser: validateEnvVar('FEDERATION_SYNAPSE_ADMIN_USER', process.env.FEDERATION_SYNAPSE_ADMIN_USER, 'admin'),
|
||||
adminPassword: validateEnvVar('FEDERATION_SYNAPSE_ADMIN_PASSWORD', process.env.FEDERATION_SYNAPSE_ADMIN_PASSWORD, 'admin'),
|
||||
url: `https://${hs1Domain}`,
|
||||
domain: hs1Domain,
|
||||
adminUser: hs1AdminUser,
|
||||
adminMatrixUserId: `@${hs1AdminUser}:${hs1Domain}`,
|
||||
adminPassword: hs1AdminPassword,
|
||||
additionalUser1: {
|
||||
username: validateEnvVar('FEDERATION_SYNAPSE_ADDITIONAL_USER1', process.env.FEDERATION_SYNAPSE_ADDITIONAL_USER1, 'alice'),
|
||||
password: validateEnvVar(
|
||||
'FEDERATION_SYNAPSE_ADDITIONAL_USER1_PASSWORD',
|
||||
process.env.FEDERATION_SYNAPSE_ADDITIONAL_USER1_PASSWORD,
|
||||
'alice',
|
||||
),
|
||||
matrixUserId: validateEnvVar(
|
||||
'FEDERATION_SYNAPSE_ADDITIONAL_USER1_MATRIX_ID',
|
||||
process.env.FEDERATION_SYNAPSE_ADDITIONAL_USER1_MATRIX_ID,
|
||||
'@alice:hs1',
|
||||
),
|
||||
username: hs1AdditionalUser1,
|
||||
password: hs1AdditionalUser1Password,
|
||||
matrixUserId: `@${hs1AdditionalUser1}:${hs1Domain}`,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@ -110,7 +110,7 @@ cleanup() {
|
||||
echo "=========================================="
|
||||
echo "CONTAINER LOGS (Test Failed)"
|
||||
echo "=========================================="
|
||||
|
||||
|
||||
echo ""
|
||||
echo "ROCKET.CHAT (rc1) LOGS:"
|
||||
echo "----------------------------------------"
|
||||
@ -119,7 +119,7 @@ cleanup() {
|
||||
else
|
||||
echo " Rocket.Chat container not found or no logs"
|
||||
fi
|
||||
|
||||
|
||||
echo ""
|
||||
echo "SYNAPSE (hs1) LOGS:"
|
||||
echo "----------------------------------------"
|
||||
@ -128,11 +128,11 @@ cleanup() {
|
||||
else
|
||||
echo " Synapse container not found or no logs"
|
||||
fi
|
||||
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
fi
|
||||
|
||||
|
||||
if [ "$KEEP_RUNNING" = true ]; then
|
||||
log_info "Keeping Docker containers running (--keep-running flag set)"
|
||||
log_info "Services are available at:"
|
||||
@ -158,12 +158,12 @@ cleanup() {
|
||||
fi
|
||||
log_success "Cleanup completed"
|
||||
fi
|
||||
|
||||
|
||||
# Remove temporary build directory if it exists
|
||||
if [ -n "${BUILD_DIR:-}" ] && [ -d "$BUILD_DIR" ]; then
|
||||
rm -rf "$BUILD_DIR" || true
|
||||
fi
|
||||
|
||||
|
||||
# Exit with the test result code
|
||||
if [ -n "${TEST_EXIT_CODE:-}" ]; then
|
||||
exit $TEST_EXIT_CODE
|
||||
@ -186,21 +186,21 @@ fi
|
||||
if [ "$USE_PREBUILT_IMAGE" = false ]; then
|
||||
log_info "🚀 Building Rocket.Chat locally..."
|
||||
log_info "====================================="
|
||||
|
||||
|
||||
# Clean up any existing build
|
||||
log_info "Cleaning up previous build..."
|
||||
rm -rf "$BUILD_DIR"
|
||||
|
||||
|
||||
# Build the project
|
||||
log_info "Building packages from project root..."
|
||||
cd "$ROCKETCHAT_ROOT"
|
||||
yarn build
|
||||
|
||||
|
||||
# Build the Meteor bundle (must be run from the meteor directory)
|
||||
log_info "Building Meteor bundle..."
|
||||
cd "$ROCKETCHAT_ROOT/apps/meteor"
|
||||
METEOR_DISABLE_OPTIMISTIC_CACHING=1 meteor build --server-only --directory "$BUILD_DIR"
|
||||
|
||||
|
||||
log_success "Build completed!"
|
||||
else
|
||||
log_info "🚀 Using pre-built image: $PREBUILT_IMAGE"
|
||||
@ -289,7 +289,7 @@ wait_for_service() {
|
||||
# Capture curl output and error for debugging
|
||||
curl_output=$(curl -fsS --cacert "$ca_cert" --resolve "${host}:${port}:127.0.0.1" "$url" 2>&1)
|
||||
curl_exit_code=$?
|
||||
|
||||
|
||||
if [ $curl_exit_code -eq 0 ]; then
|
||||
log_success "$name is ready!"
|
||||
return 0
|
||||
@ -328,8 +328,8 @@ fi
|
||||
if [ "$NO_TEST" = false ]; then
|
||||
log_info "Running end-to-end tests..."
|
||||
cd "$PACKAGE_ROOT"
|
||||
|
||||
yarn testend-to-end
|
||||
|
||||
IS_EE=true NODE_EXTRA_CA_CERTS=$(pwd)/docker-compose/traefik/certs/ca/rootCA.crt yarn test:federation
|
||||
TEST_EXIT_CODE=$?
|
||||
else
|
||||
log_info "No-test mode: skipping test execution"
|
||||
|
||||
@ -51,43 +51,103 @@ function describeImpl(name: string, fn: () => void): void {
|
||||
const currentTest = global.test;
|
||||
|
||||
// Wrap it() to automatically set suite at the very start
|
||||
global.it = ((testName: any, fn?: any, timeout?: number) => {
|
||||
// Handle qase-wrapped test names (qase returns a string)
|
||||
if (typeof testName === 'string' && fn) {
|
||||
return currentIt(
|
||||
testName,
|
||||
async () => {
|
||||
// Set suite immediately at the start of the test
|
||||
qase.suite(currentPath);
|
||||
// Call the original test function and return the result
|
||||
return fn();
|
||||
},
|
||||
timeout,
|
||||
);
|
||||
}
|
||||
// Handle cases where testName might be a number or other type
|
||||
return currentIt(testName, fn, timeout);
|
||||
}) as typeof global.it;
|
||||
global.it = Object.assign(
|
||||
(testName: any, fn?: any, timeout?: number) => {
|
||||
// Handle qase-wrapped test names (qase returns a string)
|
||||
if (typeof testName === 'string' && fn) {
|
||||
return currentIt(
|
||||
testName,
|
||||
async () => {
|
||||
// Set suite immediately at the start of the test
|
||||
qase.suite(currentPath);
|
||||
// Call the original test function and return the result
|
||||
return fn();
|
||||
},
|
||||
timeout,
|
||||
);
|
||||
}
|
||||
// Handle cases where testName might be a number or other type
|
||||
return currentIt(testName, fn, timeout);
|
||||
},
|
||||
{
|
||||
skip: (name: string, fn: () => void) => {
|
||||
suitePathStack.push(name);
|
||||
try {
|
||||
currentIt.skip(name, fn);
|
||||
} finally {
|
||||
suitePathStack.pop();
|
||||
}
|
||||
},
|
||||
only: (name: string, fn: () => void) => {
|
||||
suitePathStack.push(name);
|
||||
try {
|
||||
currentIt.only(name, fn);
|
||||
} finally {
|
||||
suitePathStack.pop();
|
||||
}
|
||||
},
|
||||
todo: (name: string) => {
|
||||
suitePathStack.push(name);
|
||||
try {
|
||||
currentIt.todo(name);
|
||||
} finally {
|
||||
suitePathStack.pop();
|
||||
}
|
||||
},
|
||||
},
|
||||
) as typeof global.it;
|
||||
|
||||
// Wrap test() to automatically set suite at the very start
|
||||
global.test = ((testName: any, fn?: any, timeout?: number) => {
|
||||
if (typeof testName === 'string' && fn) {
|
||||
return currentTest(
|
||||
testName,
|
||||
async () => {
|
||||
// Set suite immediately at the start of the test
|
||||
qase.suite(currentPath);
|
||||
// Call the original test function and return the result
|
||||
return fn();
|
||||
},
|
||||
timeout,
|
||||
);
|
||||
}
|
||||
return currentTest(testName, fn, timeout);
|
||||
}) as typeof global.test;
|
||||
global.test = Object.assign(
|
||||
(testName: any, fn?: any, timeout?: number) => {
|
||||
if (typeof testName === 'string' && fn) {
|
||||
return currentTest(
|
||||
testName,
|
||||
async () => {
|
||||
// Set suite immediately at the start of the test
|
||||
qase.suite(currentPath);
|
||||
// Call the original test function and return the result
|
||||
return fn();
|
||||
},
|
||||
timeout,
|
||||
);
|
||||
}
|
||||
return currentTest(testName, fn, timeout);
|
||||
},
|
||||
{
|
||||
skip: (name: string, fn: () => void) => {
|
||||
suitePathStack.push(name);
|
||||
try {
|
||||
currentTest.skip(name, fn);
|
||||
} finally {
|
||||
suitePathStack.pop();
|
||||
}
|
||||
},
|
||||
only: (name: string, fn: () => void) => {
|
||||
suitePathStack.push(name);
|
||||
try {
|
||||
currentTest.only(name, fn);
|
||||
} finally {
|
||||
suitePathStack.pop();
|
||||
}
|
||||
},
|
||||
todo: (name: string) => {
|
||||
suitePathStack.push(name);
|
||||
try {
|
||||
currentTest.todo(name);
|
||||
} finally {
|
||||
suitePathStack.pop();
|
||||
}
|
||||
},
|
||||
},
|
||||
) as typeof global.test;
|
||||
|
||||
// Execute the describe block
|
||||
fn();
|
||||
try {
|
||||
fn();
|
||||
} catch (error) {
|
||||
console.error('Error in describe block:', error);
|
||||
}
|
||||
|
||||
// Restore previous wrappers
|
||||
global.it = currentIt;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user