mirror of https://github.com/grafana/grafana
CloudMigrations: Add Cypress happy path test case scenarios (#103250)
parent
0fbb51ab08
commit
7165bc553a
@ -0,0 +1,298 @@ |
||||
import { e2e } from '../utils'; |
||||
|
||||
describe.skip('Migrate to Cloud (On-prem)', () => { |
||||
// Here we are mostly testing the UI flow and can do interesting things with the backend responses to see how the UI behaves.
|
||||
describe('with mocked calls to the API backend', () => { |
||||
afterEach(() => { |
||||
cy.get('[data-testid="migrate-to-cloud-summary-disconnect-button"]').should('be.visible').click(); |
||||
}); |
||||
|
||||
const SESSION_UID = 'fehq6hqd246iox'; |
||||
const SNAPSHOT_UID1 = 'cehq6vdjqbqbkx'; |
||||
|
||||
const SNAPSHOT_RESULTS = [ |
||||
{ name: 'FolderA', type: 'FOLDER', refId: 'ref-id-folder-a', parentName: 'General' }, |
||||
{ name: 'FolderB', type: 'FOLDER', refId: 'ref-id-folder-b', parentName: 'General' }, |
||||
{ name: 'Prometheus', type: 'DATASOURCE', refId: 'prometheus' }, |
||||
{ name: 'Postgres', type: 'DATASOURCE', refId: 'postgres' }, |
||||
{ name: 'Loki', type: 'DATASOURCE', refId: 'loki' }, |
||||
{ name: 'Alert Rule A', type: 'ALERT_RULE', refId: 'alert-rule-a', parentName: 'FolderA' }, |
||||
{ name: 'Alert Rule B', type: 'ALERT_RULE', refId: 'alert-rule-b', parentName: 'FolderB' }, |
||||
{ name: 'Alert Rule C', type: 'ALERT_RULE', refId: 'alert-rule-c', parentName: 'FolderB' }, |
||||
{ name: 'Alert Rule Group A', type: 'ALERT_RULE_GROUP', refId: 'alert-rule-group-a', parentName: 'FolderA' }, |
||||
{ name: 'Alert Rule Group B', type: 'ALERT_RULE_GROUP', refId: 'alert-rule-group-b', parentName: 'FolderB' }, |
||||
{ name: 'Contact Point A', type: 'CONTACT_POINT', refId: 'contact-point-a' }, |
||||
{ name: 'Contact Point B', type: 'CONTACT_POINT', refId: 'contact-point-b' }, |
||||
{ name: 'Contact Point C', type: 'CONTACT_POINT', refId: 'contact-point-c' }, |
||||
{ name: 'Notification Policy A', type: 'NOTIFICATION_POLICY', refId: 'notification-policy-a' }, |
||||
{ name: 'Notification Template A', type: 'NOTIFICATION_TEMPLATE', refId: 'notification-template-a' }, |
||||
{ name: 'Notification Template B', type: 'NOTIFICATION_TEMPLATE', refId: 'notification-template-b' }, |
||||
{ name: 'Notification Template C', type: 'NOTIFICATION_TEMPLATE', refId: 'notification-template-c' }, |
||||
{ name: 'Plugin A', type: 'PLUGIN', refId: 'plugin-a' }, |
||||
{ name: 'Plugin B', type: 'PLUGIN', refId: 'plugin-b' }, |
||||
{ name: 'Plugin C', type: 'PLUGIN', refId: 'plugin-c' }, |
||||
{ name: 'Mute Timing A', type: 'MUTE_TIMING', refId: 'mute-timing-a' }, |
||||
{ name: 'Mute Timing B', type: 'MUTE_TIMING', refId: 'mute-timing-b' }, |
||||
]; |
||||
|
||||
const MIGRATION_SESSION = { |
||||
uid: SESSION_UID, |
||||
slug: 'test-slug', |
||||
created: '2025-04-02T21:36:08+02:00', |
||||
updated: '2025-04-02T21:36:08+02:00', |
||||
}; |
||||
|
||||
const STATS = { |
||||
types: SNAPSHOT_RESULTS.reduce( |
||||
(acc, r) => { |
||||
acc[r.type] = (acc[r.type] || 0) + 1; |
||||
return acc; |
||||
}, |
||||
{} as Record<string, number> |
||||
), |
||||
statuses: { |
||||
PENDING: SNAPSHOT_RESULTS.length, |
||||
}, |
||||
total: SNAPSHOT_RESULTS.length, |
||||
}; |
||||
|
||||
it('creates and uploads a snapshot sucessfully', () => { |
||||
// Login using the UI.
|
||||
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); |
||||
|
||||
// Visit the migrate to cloud onprem page.
|
||||
e2e.pages.MigrateToCloud.visit(); |
||||
|
||||
// Open the connect modal and enter the token.
|
||||
cy.get('[data-testid="migrate-to-cloud-connect-session-modal-button"]').should('be.visible').click(); |
||||
|
||||
cy.get('[data-testid="migrate-to-cloud-connect-session-modal-token-input"]') |
||||
.should('be.visible') |
||||
.focus() |
||||
.type('test'); |
||||
|
||||
cy.intercept('POST', '/api/cloudmigration/migration', { |
||||
statusCode: 200, |
||||
body: MIGRATION_SESSION, |
||||
}).as('createMigrationToken'); |
||||
|
||||
cy.intercept('GET', '/api/cloudmigration/migration', { |
||||
statusCode: 200, |
||||
body: { |
||||
sessions: [MIGRATION_SESSION], |
||||
}, |
||||
}).as('getMigrationSessionList'); |
||||
|
||||
cy.intercept('GET', `/api/cloudmigration/migration/${SESSION_UID}/snapshots?page=1&limit=1*`, { |
||||
statusCode: 200, |
||||
body: { |
||||
snapshots: [], |
||||
}, |
||||
}).as('getSnapshotListInitial'); |
||||
|
||||
// Click the connect button to create the token.
|
||||
cy.get('[data-testid="migrate-to-cloud-connect-session-modal-connect-button"]').should('be.visible').click(); |
||||
|
||||
// Wait for the token to be created and the migration session list to be fetched to kickstart the UI state machine.
|
||||
cy.wait(['@createMigrationToken', '@getMigrationSessionList', '@getSnapshotListInitial']); |
||||
|
||||
cy.intercept('POST', `/api/cloudmigration/migration/${SESSION_UID}/snapshot`, { |
||||
statusCode: 200, |
||||
body: { |
||||
uid: SNAPSHOT_UID1, |
||||
}, |
||||
}).as('createSnapshot'); |
||||
|
||||
cy.intercept('GET', `/api/cloudmigration/migration/${SESSION_UID}/snapshots?page=1&limit=1*`, { |
||||
statusCode: 200, |
||||
body: { |
||||
snapshots: [ |
||||
{ |
||||
uid: SNAPSHOT_UID1, |
||||
sessionUid: SESSION_UID, |
||||
status: 'CREATING', |
||||
created: '2025-04-02T21:40:23+02:00', |
||||
finished: '0001-01-01T00:00:00Z', |
||||
}, |
||||
], |
||||
}, |
||||
}).as('getSnapshotListCreating'); |
||||
|
||||
let getSnapshotCalled = false; |
||||
cy.intercept( |
||||
'GET', |
||||
`/api/cloudmigration/migration/${SESSION_UID}/snapshot/${SNAPSHOT_UID1}?resultPage=1&resultLimit=50`, |
||||
(req) => { |
||||
if (!getSnapshotCalled) { |
||||
getSnapshotCalled = true; |
||||
req.reply((res) => { |
||||
res.send({ |
||||
statusCode: 200, |
||||
body: { |
||||
uid: SNAPSHOT_UID1, |
||||
sessionUid: SESSION_UID, |
||||
status: 'CREATING', |
||||
created: '2025-04-02T21:40:23+02:00', |
||||
finished: '0001-01-01T00:00:00Z', |
||||
results: [], |
||||
stats: { |
||||
types: {}, |
||||
statuses: {}, |
||||
total: 0, |
||||
}, |
||||
}, |
||||
}); |
||||
}); |
||||
} else { |
||||
req.reply((res) => { |
||||
res.send({ |
||||
statusCode: 200, |
||||
body: { |
||||
uid: SNAPSHOT_UID1, |
||||
sessionUid: SESSION_UID, |
||||
status: 'PENDING_UPLOAD', |
||||
created: '2025-04-02T21:40:23+02:00', |
||||
finished: '0001-01-01T00:00:00Z', |
||||
results: SNAPSHOT_RESULTS.map((r) => ({ ...r, status: 'PENDING' })), |
||||
stats: STATS, |
||||
}, |
||||
}); |
||||
}); |
||||
} |
||||
} |
||||
).as('getSnapshot'); |
||||
|
||||
// Build the snapshot.
|
||||
cy.get('[data-testid="migrate-to-cloud-configure-snapshot-build-snapshot-button"]').should('be.visible').click(); |
||||
|
||||
// Wait for the snapshot to be created. Simulate it going from INITIALIZING to PENDING_UPLOAD.
|
||||
cy.wait(['@createSnapshot', '@getSnapshotListCreating', '@getSnapshot']); |
||||
|
||||
cy.intercept('POST', `/api/cloudmigration/migration/${SESSION_UID}/snapshot/${SNAPSHOT_UID1}/upload`, { |
||||
statusCode: 200, |
||||
}).as('uploadSnapshot'); |
||||
|
||||
cy.intercept('GET', `/api/cloudmigration/migration/${SESSION_UID}/snapshots?page=1&limit=1*`, { |
||||
statusCode: 200, |
||||
body: { |
||||
snapshots: [ |
||||
{ |
||||
uid: SNAPSHOT_UID1, |
||||
sessionUid: SESSION_UID, |
||||
status: 'UPLOADING', |
||||
created: '2025-04-02T21:40:23+02:00', |
||||
finished: '0001-01-01T00:00:00Z', |
||||
}, |
||||
], |
||||
}, |
||||
}).as('getSnapshotListUploading'); |
||||
|
||||
// Upload the snapshot.
|
||||
cy.get('[data-testid="migrate-to-cloud-summary-upload-snapshot-button"]').should('be.visible').click(); |
||||
|
||||
// Simulate the snapshot being uploaded, the frontend will keep polling until the snapshot is either finished or errored.
|
||||
let getSnapshotUploadingCalls = 0; |
||||
cy.intercept( |
||||
'GET', |
||||
`/api/cloudmigration/migration/${SESSION_UID}/snapshot/${SNAPSHOT_UID1}?resultPage=1&resultLimit=50`, |
||||
(req) => { |
||||
req.reply((res) => { |
||||
if (getSnapshotUploadingCalls <= 1) { |
||||
res.send({ |
||||
statusCode: 200, |
||||
body: { |
||||
uid: SNAPSHOT_UID1, |
||||
sessionUid: SESSION_UID, |
||||
status: getSnapshotUploadingCalls === 1 ? 'PROCESSING' : 'UPLOADING', |
||||
created: '2025-04-02T21:40:23+02:00', |
||||
finished: '0001-01-01T00:00:00Z', |
||||
results: SNAPSHOT_RESULTS.map((r) => ({ ...r, status: 'PENDING' })), |
||||
stats: STATS, |
||||
}, |
||||
}); |
||||
getSnapshotUploadingCalls++; |
||||
} else { |
||||
res.send({ |
||||
statusCode: 200, |
||||
body: { |
||||
uid: SNAPSHOT_UID1, |
||||
sessionUid: SESSION_UID, |
||||
status: 'FINISHED', |
||||
created: '2025-03-27T12:00:00Z', |
||||
finished: '2025-03-27T12:00:00Z', |
||||
results: SNAPSHOT_RESULTS.map((r) => ({ ...r, status: 'OK' })), |
||||
stats: { |
||||
types: STATS.types, |
||||
statuses: SNAPSHOT_RESULTS.reduce( |
||||
(acc, r) => { |
||||
const status = (r as { status?: string }).status || 'UNKNOWN'; |
||||
acc[status] = (acc[status] || 0) + 1; |
||||
return acc; |
||||
}, |
||||
{} as Record<string, number> |
||||
), |
||||
total: SNAPSHOT_RESULTS.length, |
||||
}, |
||||
}, |
||||
}); |
||||
} |
||||
}); |
||||
} |
||||
).as('getSnapshotUploading'); |
||||
|
||||
// Wait for the request to kickstart the upload and then wait until it is finished.
|
||||
cy.wait(['@uploadSnapshot', '@getSnapshotListUploading', '@getSnapshotUploading']); |
||||
|
||||
// The upload button should now be hidden away.
|
||||
cy.get('[data-testid="migrate-to-cloud-summary-upload-snapshot-button"]').should('be.disabled'); |
||||
|
||||
// And the rebuild button should be visible.
|
||||
cy.get('[data-testid="migrate-to-cloud-summary-rebuild-snapshot-button"]').should('be.visible'); |
||||
|
||||
// At least some of the items are marked with "Uploaded to cloud" status.
|
||||
cy.contains('Uploaded to cloud').should('be.visible'); |
||||
}); |
||||
}); |
||||
|
||||
// Here we are doing a more black box testing of the migration flow, without explicitly mocking the API calls,
|
||||
// but we instead rely on the `[cloud_migration] developer_mode = true` to be set in the `custom.ini` file,
|
||||
// which will make the service use in-memory fake implementations of 3rdparty dependencies, but we'll still
|
||||
// use the real API endpoints, database and business logic.
|
||||
describe('with a fake GMS backend implementation', () => { |
||||
afterEach(() => { |
||||
cy.get('[data-testid="migrate-to-cloud-summary-disconnect-button"]').should('be.visible').click(); |
||||
}); |
||||
|
||||
// Manually crafted base64 token for testing, does not contain any sensitive data.
|
||||
const TEST_TOKEN = |
||||
'eyJUb2tlbiI6ImdsY19kZXZfZXlKdklqb2lNVEl6TkNJc0ltNGlPaUpuY21GbVlXNWhMV05zYjNWa0xXMXBaM0poZEdsdmJuTXRNVEl6TkNJc0ltc2lPaUowWlhOMElpd2liU0k2ZXlKeUlqb2laR1YyTFhWekxXTmxiblJ5WVd3aWZYMEsiLCJJbnN0YW5jZSI6eyJTdGFja0lEIjoxMjM0LCJTbHVnIjoidGVzdC1zbHVnIiwiUmVnaW9uU2x1ZyI6ImRldi11cy1jZW50cmFsIiwiQ2x1c3RlclNsdWciOiJkZXYtdXMtY2VudHJhbC0wIn19Cg=='; |
||||
|
||||
it('creates a snapshot sucessfully', () => { |
||||
// Login using the UI.
|
||||
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); |
||||
|
||||
// Visit the migrate to cloud onprem page.
|
||||
e2e.pages.MigrateToCloud.visit(); |
||||
|
||||
// Open the connect modal and enter the token.
|
||||
cy.get('[data-testid="migrate-to-cloud-connect-session-modal-button"]').should('be.visible').click(); |
||||
|
||||
cy.get('[data-testid="migrate-to-cloud-connect-session-modal-token-input"]') |
||||
.should('be.visible') |
||||
.focus() |
||||
.type(TEST_TOKEN); |
||||
|
||||
// Click the connect button to create the token.
|
||||
cy.get('[data-testid="migrate-to-cloud-connect-session-modal-connect-button"]').should('be.visible').click(); |
||||
|
||||
// Build the snapshot.
|
||||
cy.get('[data-testid="migrate-to-cloud-configure-snapshot-build-snapshot-button"]').should('be.visible').click(); |
||||
|
||||
// And the rebuild button should be visible.
|
||||
cy.get('[data-testid="migrate-to-cloud-summary-rebuild-snapshot-button"]').should('be.visible'); |
||||
|
||||
// We don't upload the snapshot yet because we need to create a mock server to validate the uploaded items,
|
||||
// similarly to what the SMTP (tester) server does.
|
||||
}); |
||||
}); |
||||
}); |
Loading…
Reference in new issue