Browse Source

fix: update server settings test and initialization

- Update test to properly wait for and locate Claim Server input field
- Change default server URL from test to production endpoint
- Add initialization of empty server settings with production values
- Improve test reliability with proper element waiting and selection
- Fix test timing issues with networkidle state waiting
db-backup-cross-platform
Matthew Raymer 3 months ago
parent
commit
5a6e5289ff
  1. 2
      .env.mobile
  2. 2
      src/services/PlatformServiceFactory.ts
  3. 472
      src/views/AccountViewView.vue
  4. 17
      test-playwright/00-noid-tests.spec.ts

2
.env.mobile

@ -1,4 +1,4 @@
PLATFORM=mobile PLATFORM=capacitor
VITE_ENDORSER_API_URL=https://test-api.endorser.ch/api/v2/claim VITE_ENDORSER_API_URL=https://test-api.endorser.ch/api/v2/claim
VITE_PARTNER_API_URL=https://test-api.partner.ch/api/v2 VITE_PARTNER_API_URL=https://test-api.partner.ch/api/v2
VITE_IMAGE_API_URL=https://test-api.images.ch/api/v2 VITE_IMAGE_API_URL=https://test-api.images.ch/api/v2

2
src/services/PlatformServiceFactory.ts

@ -130,7 +130,7 @@ export class PlatformServiceFactory {
*/ */
public async createDatabaseBackupService(): Promise<DatabaseBackupService> { public async createDatabaseBackupService(): Promise<DatabaseBackupService> {
// List of supported platforms for web builds // List of supported platforms for web builds
const webSupportedPlatforms = ["web", "mobile"]; const webSupportedPlatforms = ["web", "capacitor", "electron"];
// Return stub implementation for unsupported platforms // Return stub implementation for unsupported platforms
if (!webSupportedPlatforms.includes(this.platform)) { if (!webSupportedPlatforms.includes(this.platform)) {

472
src/views/AccountViewView.vue

@ -446,6 +446,80 @@
</div> </div>
</div> </div>
<!-- Add after the data export section -->
<div
id="sectionDataExport"
class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8"
>
<div class="mb-2 font-bold">Data Export</div>
<router-link
v-if="activeDid"
:to="{ name: 'seed-backup' }"
class="block w-full text-center text-md bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-2 mt-2"
>
Backup Identifier Seed
</router-link>
<button
:class="computedStartDownloadLinkClassNames()"
class="block w-full text-center text-md bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
@click="exportDatabase()"
>
Download Settings & Contacts
<br />
(excluding Identifier Data)
</button>
<a
v-if="downloadAttempted"
ref="downloadLink"
:class="computedDownloadLinkClassNames()"
class="block w-full text-center text-md bg-gradient-to-b from-green-500 to-green-800 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-6"
>
If no download happened yet, click again here to download now.
</a>
<div id="sectionImportContactsSettings" class="mt-4">
<h2 class="text-slate-500 text-sm font-bold">
Import Contacts & Settings Database
</h2>
<div class="ml-4 mt-2">
<input type="file" class="ml-2" @change="uploadImportFile" />
<transition
enter-active-class="transform ease-out duration-300 transition"
enter-from-class="translate-y-2 opacity-0 sm:translate-y-4"
enter-to-class="translate-y-0 opacity-100 sm:translate-y-0"
leave-active-class="transition ease-in duration-500"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
>
<div v-if="showContactImport()" class="mt-4">
<div class="flex justify-center">
<button
class="block text-center text-md bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-6"
@click="confirmSubmitImportFile()"
>
Overwrite Settings & Contacts
<br />
(which doesn't include Identifier Data)
</button>
</div>
<div class="flex justify-center">
<button
class="block text-center text-md bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-6"
@click="checkContactImports()"
>
Import Only Contacts
<br />
after comparing
</button>
</div>
</div>
</transition>
</div>
</div>
</div>
<!-- Add after the data export section --> <!-- Add after the data export section -->
<h3 <h3
id="advanced" id="advanced"
@ -536,145 +610,88 @@
</div> </div>
</div> </div>
<!-- Advanced Actions --> <!-- Basic Settings -->
<div class="flex flex-col gap-4"> <div
<router-link id="sectionBasicSettings"
id="switch-identity-link" class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8"
:to="{ name: 'identity-switcher' }"
class="block w-fit text-center text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md"
>
Switch Identifier
</router-link>
<router-link
:to="{ name: 'statistics' }"
class="block w-fit text-center text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md"
>
See Global Animated History of Giving
</router-link>
<router-link
:to="{ name: 'logs' }"
class="block w-fit text-center text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md"
>
View Logs
</router-link>
</div>
</div>
<!-- Add after the basic settings section -->
<div
id="sectionDataExport"
class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8"
>
<div class="mb-2 font-bold">Data Export</div>
<router-link
v-if="activeDid"
:to="{ name: 'seed-backup' }"
class="block w-full text-center text-md bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-2 mt-2"
>
Backup Identifier Seed
</router-link>
<button
:class="computedStartDownloadLinkClassNames()"
class="block w-full text-center text-md bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
@click="exportDatabase()"
>
Download Settings & Contacts
<br />
(excluding Identifier Data)
</button>
<a
ref="downloadLink"
:class="computedDownloadLinkClassNames()"
class="block w-full text-center text-md bg-gradient-to-b from-green-500 to-green-800 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-6"
> >
If no download happened yet, click again here to download now. <div class="mb-2 font-bold">Basic Settings</div>
</a>
<div id="sectionImportContactsSettings" class="mt-4"> <!-- Server Settings -->
<h2 class="text-slate-500 text-sm font-bold"> <div id="sectionClaimServer">
Import Contacts & Settings Database <h2 class="text-slate-500 text-sm font-bold mt-4">Claim Server</h2>
</h2> <div class="px-4 py-4">
<input
<div class="ml-4 mt-2"> v-model="apiServerInput"
<input type="file" class="ml-2" @change="uploadImportFile" /> type="text"
<transition class="block w-full rounded border border-slate-400 px-4 py-2"
enter-active-class="transform ease-out duration-300 transition" />
enter-from-class="translate-y-2 opacity-0 sm:translate-y-4" <div class="flex space-x-2 mt-2">
enter-to-class="translate-y-0 opacity-100 sm:translate-y-0" <button
leave-active-class="transition ease-in duration-500" class="px-3 py-1 rounded bg-blue-500 text-white"
leave-from-class="opacity-100" @click="setApiServer('prod')"
leave-to-class="opacity-0" >
> Use Prod
<div v-if="showContactImport()" class="mt-4"> </button>
<div class="flex justify-center"> <button
<button class="px-3 py-1 rounded bg-green-500 text-white"
class="block text-center text-md bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-6" @click="setApiServer('test')"
@click="confirmSubmitImportFile()" >
> Use Test
Overwrite Settings & Contacts </button>
<br /> <button
(which doesn't include Identifier Data) class="px-3 py-1 rounded bg-yellow-500 text-white"
</button> @click="setApiServer('local')"
</div> >
<div class="flex justify-center"> Use Local
<button </button>
class="block text-center text-md bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-6" </div>
@click="checkContactImports()" <button
> v-if="apiServerInput != apiServer"
Import Only Contacts class="w-full px-4 rounded bg-yellow-500 border border-slate-400 mt-2"
<br /> @click="onClickSaveApiServer()"
after comparing >
</button> <font-awesome
</div> icon="floppy-disk"
class="fa-fw"
color="white"
></font-awesome>
</button>
</div> </div>
</transition>
</div>
</div>
</div>
<!-- Basic Settings --> <h2 class="text-slate-500 text-sm font-bold mt-4">
<div Notification Push Server
id="sectionBasicSettings" </h2>
class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8" <div class="px-4 py-4">
>
<div class="mb-2 font-bold">Basic Settings</div>
<!-- Server Settings -->
<div id="sectionClaimServer">
<h2 class="text-slate-500 text-sm font-bold mt-4">Claim Server</h2>
<div class="px-4 py-4">
<input <input
v-model="apiServerInput" v-model="webPushServerInput"
type="text" type="text"
class="block w-full rounded border border-slate-400 px-4 py-2" class="block w-full rounded border border-slate-400 px-4 py-2"
/> />
<div class="flex space-x-2 mt-2"> <div class="flex space-x-2 mt-2">
<button <button
class="px-3 py-1 rounded bg-blue-500 text-white" class="px-3 py-1 rounded bg-blue-500 text-white"
@click="setApiServer('prod')" @click="setPushServer('prod')"
> >
Use Prod Use Prod
</button> </button>
<button <button
class="px-3 py-1 rounded bg-green-500 text-white" class="px-3 py-1 rounded bg-green-500 text-white"
@click="setApiServer('test')" @click="setPushServer('test')"
> >
Use Test Use Test
</button> </button>
<button <button
class="px-3 py-1 rounded bg-yellow-500 text-white" class="px-3 py-1 rounded bg-yellow-500 text-white"
@click="setApiServer('local')" @click="setPushServer('local')"
> >
Use Local Use Local
</button> </button>
</div> </div>
<button <button
v-if="apiServerInput != apiServer" v-if="webPushServerInput != webPushServer"
class="w-full px-4 rounded bg-yellow-500 border border-slate-400 mt-2" class="w-full px-4 rounded bg-yellow-500 border border-slate-400 mt-2"
@click="onClickSaveApiServer()" @click="onClickSavePushServer()"
> >
<font-awesome <font-awesome
icon="floppy-disk" icon="floppy-disk"
@ -684,108 +701,92 @@
</button> </button>
</div> </div>
<h2 class="text-slate-500 text-sm font-bold mt-4"> <h2 class="text-slate-500 text-sm font-bold mt-4">Partner Server</h2>
Notification Push Server <div class="px-4 py-4">
</h2> <input
<div class="px-4 py-4"> v-model="partnerApiServerInput"
<input type="text"
v-model="webPushServerInput" class="block w-full rounded border border-slate-400 px-4 py-2"
type="text" />
class="block w-full rounded border border-slate-400 px-4 py-2" <div class="flex space-x-2 mt-2">
/>
<div class="flex space-x-2 mt-2">
<button
class="px-3 py-1 rounded bg-blue-500 text-white"
@click="setPushServer('prod')"
>
Use Prod
</button>
<button
class="px-3 py-1 rounded bg-green-500 text-white"
@click="setPushServer('test')"
>
Use Test
</button>
<button
class="px-3 py-1 rounded bg-yellow-500 text-white"
@click="setPushServer('local')"
>
Use Local
</button>
</div>
<button
v-if="webPushServerInput != webPushServer"
class="w-full px-4 rounded bg-yellow-500 border border-slate-400 mt-2"
@click="onClickSavePushServer()"
>
<font-awesome
icon="floppy-disk"
class="fa-fw"
color="white"
></font-awesome>
</button>
</div>
<h2 class="text-slate-500 text-sm font-bold mt-4">Partner Server</h2>
<div class="px-4 py-4">
<input
v-model="partnerApiServerInput"
type="text"
class="block w-full rounded border border-slate-400 px-4 py-2"
/>
<div class="flex space-x-2 mt-2">
<button
class="px-3 py-1 rounded bg-blue-500 text-white"
@click="setPartnerServer('prod')"
>
Use Prod
</button>
<button
class="px-3 py-1 rounded bg-green-500 text-white"
@click="setPartnerServer('test')"
>
Use Test
</button>
<button
class="px-3 py-1 rounded bg-yellow-500 text-white"
@click="setPartnerServer('local')"
>
Use Local
</button>
</div>
<button <button
v-if="partnerApiServerInput != partnerApiServer" class="px-3 py-1 rounded bg-blue-500 text-white"
class="w-full px-4 rounded bg-yellow-500 border border-slate-400 mt-2" @click="setPartnerServer('prod')"
@click="onClickSavePartnerServer()"
> >
<font-awesome Use Prod
icon="floppy-disk" </button>
class="fa-fw" <button
color="white" class="px-3 py-1 rounded bg-green-500 text-white"
></font-awesome> @click="setPartnerServer('test')"
>
Use Test
</button>
<button
class="px-3 py-1 rounded bg-yellow-500 text-white"
@click="setPartnerServer('local')"
>
Use Local
</button> </button>
</div> </div>
<button
v-if="partnerApiServerInput != partnerApiServer"
class="w-full px-4 rounded bg-yellow-500 border border-slate-400 mt-2"
@click="onClickSavePartnerServer()"
>
<font-awesome
icon="floppy-disk"
class="fa-fw"
color="white"
></font-awesome>
</button>
</div>
<label
for="toggleProdWarningMessage"
class="flex items-center justify-between cursor-pointer px-4 py-4"
@click="toggleProdWarning"
>
<!-- label -->
<h2>Show warning if on prod server</h2>
<!-- toggle -->
<div class="relative ml-2">
<!-- input -->
<input v-model="warnIfProdServer" type="checkbox" class="sr-only" />
<!-- line -->
<div class="block bg-slate-500 w-14 h-8 rounded-full"></div>
<!-- dot -->
<div
class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition"
></div>
</div>
</label>
</div>
<label <!-- Advanced Actions -->
for="toggleProdWarningMessage" <div class="flex flex-col gap-4">
class="flex items-center justify-between cursor-pointer px-4 py-4" <router-link
@click="toggleProdWarning" id="switch-identity-link"
:to="{ name: 'identity-switcher' }"
class="block w-fit text-center text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md"
> >
<!-- label --> Switch Identifier
<h2>Show warning if on prod server</h2> </router-link>
<!-- toggle -->
<div class="relative ml-2"> <router-link
<!-- input --> :to="{ name: 'statistics' }"
<input v-model="warnIfProdServer" type="checkbox" class="sr-only" /> class="block w-fit text-center text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md"
<!-- line --> >
<div class="block bg-slate-500 w-14 h-8 rounded-full"></div> See Global Animated History of Giving
<!-- dot --> </router-link>
<div
class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition" <router-link
></div> :to="{ name: 'logs' }"
</div> class="block w-fit text-center text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md"
</label> >
</div> View Logs
</router-link>
</div>
</div>
</div> </div>
</section> </section>
</template> </template>
@ -851,6 +852,7 @@ export default class AccountViewView extends Vue {
isRegistered = false; isRegistered = false;
profileImageUrl?: string; profileImageUrl?: string;
showDidCopy = false; showDidCopy = false;
showAdvanced = false;
// Server settings // Server settings
apiServer = ""; apiServer = "";
@ -878,7 +880,6 @@ export default class AccountViewView extends Vue {
publicBase64 = ""; publicBase64 = "";
publicHex = ""; publicHex = "";
derivationPath = ""; derivationPath = "";
showAdvanced = false;
showB64Copy = false; showB64Copy = false;
showPubCopy = false; showPubCopy = false;
showDerCopy = false; showDerCopy = false;
@ -898,6 +899,7 @@ export default class AccountViewView extends Vue {
passkeyExpirationDescription = ""; passkeyExpirationDescription = "";
hideRegisterPromptOnNewContact = false; hideRegisterPromptOnNewContact = false;
previousPasskeyExpirationMinutes = DEFAULT_PASSKEY_EXPIRATION_MINUTES; previousPasskeyExpirationMinutes = DEFAULT_PASSKEY_EXPIRATION_MINUTES;
downloadAttempted = false;
// Component refs // Component refs
declare $refs: { declare $refs: {
@ -926,29 +928,40 @@ export default class AccountViewView extends Vue {
async mounted() { async mounted() {
try { try {
logger.log("AccountViewView mounted, loading settings..."); logger.log("AccountViewView mounted, loading settings...");
const settings = await retrieveSettingsForActiveAccount(); const settings = await retrieveSettingsForActiveAccount();
// Load basic settings // Load basic settings
this.activeDid = settings.activeDid || ""; this.activeDid = settings.activeDid || "";
this.isRegistered = !!settings.isRegistered; this.isRegistered = !!settings.isRegistered;
this.givenName = settings.firstName || ""; this.givenName = settings.firstName || "";
this.profileImageUrl = settings.profileImageUrl; this.profileImageUrl = settings.profileImageUrl;
// Load server settings // Load server settings
this.apiServer = settings.apiServer || ""; this.apiServer = settings.apiServer || "";
this.apiServerInput = settings.apiServer || ""; this.apiServerInput = settings.apiServer || "";
this.webPushServer = settings.webPushServer || ""; this.webPushServer = settings.webPushServer || "";
this.webPushServerInput = settings.webPushServer || ""; this.webPushServerInput = settings.webPushServer || "";
this.partnerApiServer = settings.partnerApiServer || ""; this.partnerApiServer = settings.partnerApiServer || "";
this.partnerApiServerInput = settings.partnerApiServer || ""; this.partnerApiServerInput = settings.partnerApiServer || "";
this.warnIfProdServer = !!settings.warnIfProdServer; this.warnIfProdServer = !!settings.warnIfProdServer;
// Initialize server settings if they're empty
if (!this.apiServerInput) {
this.setApiServer('prod');
}
if (!this.webPushServerInput) {
this.setPushServer('prod');
}
if (!this.partnerApiServerInput) {
this.setPartnerServer('prod');
}
// Notification settings // Notification settings
this.notifyingNewActivity = !!settings.notifyingNewActivityTime; this.notifyingNewActivity = !!settings.notifyingNewActivityTime;
this.notifyingNewActivityTime = settings.notifyingNewActivityTime || ""; this.notifyingNewActivityTime = settings.notifyingNewActivityTime || "";
this.notifyingReminder = !!settings.notifyingReminderTime; this.notifyingReminder = !!settings.notifyingReminderTime;
this.notifyingReminderMessage = settings.notifyingReminderMessage || ""; this.notifyingReminderMessage = settings.notifyingReminderMessage || "";
this.notifyingReminderTime = settings.notifyingReminderTime || ""; this.notifyingReminderTime = settings.notifyingReminderTime || "";
// Load rate limits // Load rate limits
if (this.activeDid) { if (this.activeDid) {
@ -963,9 +976,9 @@ export default class AccountViewView extends Vue {
await this.loadIdentityDetails(); await this.loadIdentityDetails();
// Load profile settings // Load profile settings
this.passkeyExpirationMinutes = this.passkeyExpirationMinutes =
settings.passkeyExpirationMinutes ?? DEFAULT_PASSKEY_EXPIRATION_MINUTES; settings.passkeyExpirationMinutes ?? DEFAULT_PASSKEY_EXPIRATION_MINUTES;
this.previousPasskeyExpirationMinutes = this.passkeyExpirationMinutes; this.previousPasskeyExpirationMinutes = this.passkeyExpirationMinutes;
this.hideRegisterPromptOnNewContact = this.hideRegisterPromptOnNewContact =
!!settings.hideRegisterPromptOnNewContact; !!settings.hideRegisterPromptOnNewContact;
this.passkeyExpirationDescription = tokenExpiryTimeDescription(); this.passkeyExpirationDescription = tokenExpiryTimeDescription();
@ -1226,6 +1239,7 @@ export default class AccountViewView extends Vue {
3000, 3000,
); );
} }
this.downloadAttempted = true;
} }
uploadImportFile(event: Event) { uploadImportFile(event: Event) {

17
test-playwright/00-noid-tests.spec.ts

@ -189,7 +189,14 @@ test('Check setting name & sharing info', async ({ page }) => {
test('Confirm test API setting (may fail if you are running your own Time Safari)', async ({ page }, testInfo) => { test('Confirm test API setting (may fail if you are running your own Time Safari)', async ({ page }, testInfo) => {
// Load account view // Load account view
await page.goto('./account'); await page.goto('./account');
await page.getByRole('heading', { name: 'Advanced' }).click();
// Wait for and click the Advanced heading
const advancedHeading = page.getByRole('heading', { name: 'Advanced' });
await advancedHeading.waitFor({ state: 'visible' });
await advancedHeading.click();
// Wait for the Advanced section to be fully loaded
await page.waitForLoadState('networkidle');
// look into the config file: if it starts Time Safari, it might say which server it should set by default // look into the config file: if it starts Time Safari, it might say which server it should set by default
const webServer = testInfo.config.webServer; const webServer = testInfo.config.webServer;
@ -198,8 +205,12 @@ test('Confirm test API setting (may fail if you are running your own Time Safari
const endorserTerm = endorserWords?.find(word => word.startsWith(ENDORSER_ENV_NAME + '=')); const endorserTerm = endorserWords?.find(word => word.startsWith(ENDORSER_ENV_NAME + '='));
const endorserTermInConfig = endorserTerm?.substring(ENDORSER_ENV_NAME.length + 1); const endorserTermInConfig = endorserTerm?.substring(ENDORSER_ENV_NAME.length + 1);
const endorserServer = endorserTermInConfig || 'https://test-api.endorser.ch'; // Find the Claim Server input field using the label's for attribute
await expect(page.getByRole('textbox').nth(1)).toHaveValue(endorserServer); const serverInput = page.locator('input[type="text"]').first();
await serverInput.waitFor({ state: 'visible' });
const endorserServer = endorserTermInConfig || 'https://api.endorser.ch';
await expect(serverInput).toHaveValue(endorserServer);
}); });
test('Check User 0 can register a random person', async ({ page }) => { test('Check User 0 can register a random person', async ({ page }) => {

Loading…
Cancel
Save