Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Storage tests pass #1965

Merged
merged 9 commits into from
Feb 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/files-ui/cypress/support/utils/apiTestHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import { BucketType } from "@chainsafe/files-api-client"
import { navigationMenu } from "../page-objects/navigationMenu"
import { homePage } from "../page-objects/homePage"

const API_BASE_USE = "https://stage.imploy.site/api/v1"
const API_BASE_URL = "https://stage.imploy.site/api/v1"
const REFRESH_TOKEN_KEY = "csf.refreshToken"
const FREE_PLAN_ID = "prod_JwRu6Ph25b1f2O"

export type ClearBucketType = Exclude<BucketType, "share" | "pinning" | "fps">
const getApiClient = () => {
// Disable the internal Axios JSON deserialization as this is handled by the client
const axiosInstance = axios.create({ transformResponse: [] })
const apiClient = new FilesApiClient({}, API_BASE_USE, axiosInstance)
const apiClient = new FilesApiClient({}, API_BASE_URL, axiosInstance)

return apiClient
}
Expand Down
54 changes: 37 additions & 17 deletions packages/storage-ui/cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,43 +33,50 @@ import { testPrivateKey, localHost } from "../fixtures/loginData"
import { CustomizedBridge } from "./utils/CustomBridge"
import "cypress-file-upload"
import "cypress-pipe"
import { BucketType } from "@chainsafe/files-api-client"
import { navigationMenu } from "./page-objects/navigationMenu"

export type Storage = Record<string, string>[];

export interface Web3LoginOptions {
url?: string
apiUrlBase?: string
saveBrowser?: boolean
withNewUser?: boolean
clearPins?: boolean
deleteFpsBuckets?: boolean
}

Cypress.Commands.add("clearPins", (apiUrlBase: string) => {
apiTestHelper.clearPins(apiUrlBase)
Cypress.Commands.add("clearPins", apiTestHelper.clearPins)

Cypress.Commands.add("deleteBuckets", (type: BucketType | BucketType[]) => {
apiTestHelper.deleteBuckets(type)
})

Cypress.Commands.add(
"web3Login",
({
url = localHost,
apiUrlBase = "https://stage.imploy.site/api/v1",
clearPins = false
clearPins = false,
withNewUser = true,
deleteFpsBuckets = false
}: Web3LoginOptions = {}) => {

cy.on("window:before:load", (win) => {
const provider = new ethers.providers.JsonRpcProvider(
"https://rinkeby.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847",
4
)
const signer = new Wallet(testPrivateKey, provider)
const signer = withNewUser
? Wallet.createRandom()
: new Wallet(testPrivateKey, provider)
// inject ethereum object in the global window
Object.defineProperty(win, "ethereum", {
get: () => new CustomizedBridge(signer as any, provider as any)
get: () => new CustomizedBridge(signer as any, signer.address, provider as any)
})
})

// with nothing in localstorage (and in session storage)
// the whole login flow should kick in
cy.session("web3login", () => {
cy.session("web3loginNewUser", () => {
cy.visit(url)
authenticationPage.web3Button().click()
authenticationPage.showMoreButton().click()
Expand All @@ -82,12 +89,18 @@ Cypress.Commands.add(
bucketsPage.bucketsHeaderLabel().should("be.visible")

if(clearPins){
cy.clearPins(apiUrlBase)
cy.clearPins()
navigationMenu.bucketsNavButton().click()
navigationMenu.cidsNavButton().click()
}

if (deleteFpsBuckets) {
apiTestHelper.deleteBuckets("fps")
}
}
)

Cypress.Commands.add("safeClick", { prevSubject: "element" }, $element => {
Cypress.Commands.add("safeClick", { prevSubject: "element" }, ($element?: JQuery<HTMLElement>) => {
const click = ($el: JQuery<HTMLElement>) => $el.trigger("click")
return cy
.wrap($element)
Expand All @@ -105,16 +118,15 @@ declare global {
/**
* Login using Metamask to an instance of Storage.
* @param {String} options.url - (default: "http://localhost:3000") - what url to visit.
* @param {String} apiUrlBase - (default: "https://stage.imploy.site/api/v1") - what url to call for the api.
* @param {Boolean} options.withNewUser - (default: true) - whether to create a new user for this session.
* @param {Boolean} options.clearCSFBucket - (default: false) - whether any file in the csf bucket should be deleted.
*/
web3Login: (options?: Web3LoginOptions) => Chainable
web3Login: (options?: Web3LoginOptions) => void

/**
* Remove all "queued", "pinning", "pinned", "failed" pins
* @param {String} apiUrlBase - what url to call for the api.
* @example cy.clearPins("https://stage.imploy.site/api/v1")
*/
clearPins: (apiUrlBase: string) => Chainable
clearPins: () => void

/**
* Use this when encountering race condition issues resulting in
Expand All @@ -128,7 +140,15 @@ declare global {
* https://github.com/cypress-io/cypress/issues/7306
*
*/
safeClick: () => Chainable
safeClick: ($element?: JQuery<HTMLElement>) => Chainable

/**
* Clear a bucket.
* @param {BucketType} - what bucket type to clear for this user.
* @example cy.deleteBuckets("fps")
* @example cy.deleteBuckets(["fps","csf"])
*/
deleteBuckets: (type: BucketType | BucketType[]) => void
}
}
}
Expand Down
15 changes: 13 additions & 2 deletions packages/storage-ui/cypress/support/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,16 @@ Cypress.on("uncaught:exception", (err) => {
}
})

// Alternatively you can use CommonJS syntax:
// require('./commands')
// Hide fetch/XHR requests
// interim solution until cypress adds configuration support
// source https://gist.github.com/simenbrekken/3d2248f9e50c1143bf9dbe02e67f5399
const app = window.top

if(app != null && !app.document.head.querySelector("[data-hide-command-log-request]")) {
const style = app.document.createElement("style")
style.innerHTML =
".command-name-request, .command-name-xhr { display: none }"
style.setAttribute("data-hide-command-log-request", "")

app.document.head.appendChild(style)
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const bucketsPage = {
nameTableHeader: () => cy.get("[data-cy=table-header-name]"),
sizeTableHeader: () => cy.get("[data-cy=table-header-size]"),
bucketItemName: () => cy.get("[data-cy=cell-bucket-name]"),
bucketRowKebabButton: () => cy.get("[data-testid=dropdown-title-bucket-kebab]"),
bucketRowKebabButton: () => cy.get("[data-testid=dropdown-title-bucket-kebab]", { timeout: 10000 }),

// create bucket modal elements
createBucketForm: () => cy.get("[data-testid=form-create-bucket]", { timeout: 10000 }),
Expand Down
19 changes: 13 additions & 6 deletions packages/storage-ui/cypress/support/utils/CustomBridge.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import { Eip1193Bridge } from "@ethersproject/experimental/lib/eip1193-bridge"
import { ethers } from "ethers"
import { toUtf8String } from "ethers/lib/utils"
import { testAddress } from "../../fixtures/loginData"

export class CustomizedBridge extends Eip1193Bridge {
expectedAddress = ""

constructor(signer: ethers.Signer, address: string, provider?: ethers.providers.Provider) {
super(signer as any, provider as any)
this.expectedAddress = address
}

async sendAsync(...args: Array<any>) {
return this.send(...args)
}
Expand Down Expand Up @@ -30,17 +37,17 @@ export class CustomizedBridge extends Eip1193Bridge {
const message = params[0]

if (
(addr as string).toLowerCase() !== testAddress.toLowerCase()
(addr as string).toLowerCase() !== this.expectedAddress.toLowerCase()
) {
return Promise.reject(
`Wrong address, expected ${testAddress}, but got ${addr}`
`Wrong address, expected ${this.expectedAddress}, but got ${addr}`
)
}

try {
const sig = await this.signer.signMessage(toUtf8String(message))
return sig
} catch (e) {
} catch (e: any) {
return Promise.reject(
`Error in CustomizedBridge for personal_sign: ${e.message}`
)
Expand All @@ -49,9 +56,9 @@ export class CustomizedBridge extends Eip1193Bridge {

if (method === "eth_requestAccounts" || method === "eth_accounts") {
if (isCallbackForm) {
callback({ result: [testAddress] })
callback({ result: [this.expectedAddress] })
} else {
return Promise.resolve([testAddress])
return Promise.resolve([this.expectedAddress])
}
}

Expand Down
46 changes: 37 additions & 9 deletions packages/storage-ui/cypress/support/utils/apiTestHelper.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,54 @@
import axios from "axios"
import { FilesApiClient } from "@chainsafe/files-api-client"
import { BucketType } from "@chainsafe/files-api-client"

const REFRESH_TOKEN_KEY = "css.refreshToken"
const API_BASE_URL = "https://stage.imploy.site/api/v1"

const getApiClient = () => {
// Disable the internal Axios JSON deserialization as this is handled by the client
const axiosInstance = axios.create({ transformResponse: [] })
const apiClient = new FilesApiClient({}, API_BASE_URL, axiosInstance)

return apiClient
}

export const apiTestHelper = {
clearPins(apiUrlBase: string) {
const axiosInstance = axios.create({
// Disable the internal Axios JSON de serialization as this is handled by the client
transformResponse: []
})
const apiClient = new FilesApiClient({}, apiUrlBase, axiosInstance)
clearPins() {
const apiClient = getApiClient()

cy.window().then((win) => {
apiClient
.getRefreshToken({
refresh: win.localStorage.getItem(REFRESH_TOKEN_KEY) || ""
})
.then((tokens) => {
apiClient.setToken(tokens.access_token.token)
apiClient.listPins(undefined, undefined, ["queued", "pinning", "pinned", "failed"])
.then((pins) =>
// The ones in "queued" and "pinning" status can't be deleted
apiClient.listPins(undefined, undefined, ["pinned", "failed"])
.then((pins) =>
pins.results?.forEach(ps => apiClient.deletePin(ps.requestid)
))
))
})
})
},
deleteBuckets(type: BucketType | BucketType[]) {
const apiClient = getApiClient()
const typeToDelete = Array.isArray(type) ? type : [type]

return new Cypress.Promise(async (resolve) => {
cy.window()
.then(async (win) => {
const tokens = await apiClient.getRefreshToken({ refresh: win.localStorage.getItem(REFRESH_TOKEN_KEY) || "" })

await apiClient.setToken(tokens.access_token.token)
const buckets = await apiClient.listBuckets(typeToDelete)
buckets.forEach(async (bucket) => {
cy.log(`Deleting fps bucket: "${bucket.name}""`)
await apiClient.removeBucket(bucket.id)
})
cy.log("Done deleting fps buckets.")
resolve()
})
})
}
Expand Down
17 changes: 9 additions & 8 deletions packages/storage-ui/cypress/tests/bucket-management-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ describe("Bucket management", () => {

context("desktop", () => {

it.skip("can create a bucket", () => {
cy.web3Login({ clearPins: true })
it("can create a bucket", () => {
cy.web3Login({ clearPins: true, deleteFpsBuckets: true })

// create a bucket and see it in the bucket table
navigationMenu.bucketsNavButton().click()
Expand All @@ -24,16 +24,17 @@ describe("Bucket management", () => {
bucketsPage.createBucketForm().should("not.exist")
})

it.skip("can delete a bucket", () => {
cy.web3Login({ clearPins: true })
it("can delete a bucket", () => {
cy.web3Login({ clearPins: true, deleteFpsBuckets: true })

// delete a bucket and ensure it's row is removed
// delete a bucket and ensure its row is removed
navigationMenu.bucketsNavButton().click()
bucketsPage.createBucket(bucketName)
// creating a bucket with a unique name
bucketsPage.createBucket(`${bucketName}_${Date.now()}`)
bucketsPage.bucketRowKebabButton().first().click()
bucketsPage.deleteBucketMenuOption().first().click()
bucketsPage.bucketItemRow().should("not.be.visible")
bucketsPage.bucketItemName().should("not.be.visible")
bucketsPage.bucketItemRow().should("not.exist")
bucketsPage.bucketItemName().should("not.exist")
})
})
})
6 changes: 4 additions & 2 deletions packages/storage-ui/cypress/tests/cid-management-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ describe("CID management", () => {

context("desktop", () => {

it.skip("can pin a CID", () => {
it("can pin a CID", () => {
cy.web3Login({ clearPins: true })
navigationMenu.cidsNavButton().click()

Expand All @@ -22,14 +22,16 @@ describe("CID management", () => {
cidsPage.pinCidForm().should("not.exist")
})

// this is unreliable since the pin from the previous
// test is still in the "queued" state while being unpinned.
it.skip("can unpin a cid", () => {
cy.web3Login({ clearPins: true })

// pin and then unpin a CID
cidsPage.addPinnedCid()
cidsPage.cidRowKebabButton().click()
cidsPage.unpinMenuOption().click()
cidsPage.cidItemRow().should("not.exist")
cidsPage.cidItemRow().should("contain.text", "queued")
Tbaut marked this conversation as resolved.
Show resolved Hide resolved
})
})
})
27 changes: 18 additions & 9 deletions packages/storage-ui/src/Components/Pages/BucketsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useMemo, useState } from "react"
import React, { useEffect, useMemo, useState } from "react"
import { makeStyles, createStyles } from "@chainsafe/common-theme"
import {
Button,
Expand Down Expand Up @@ -114,10 +114,18 @@ const useStyles = makeStyles(({ breakpoints, animation, constants, typography }:

const BucketsPage = () => {
const classes = useStyles()
const { storageBuckets, createBucket } = useStorage()
const { storageBuckets, createBucket, refreshBuckets } = useStorage()
const [isCreateBucketModalOpen, setIsCreateBucketModalOpen] = useState(false)
const bucketsToShow = useMemo(() => storageBuckets.filter(b => b.status === "created"), [storageBuckets])
const bucketNameValidationSchema = useMemo(
() => bucketNameValidator(bucketsToShow.map(b => b.name))
, [bucketsToShow]
)

const bucketNameValidationSchema = useMemo(() => bucketNameValidator(storageBuckets.map(b => b.name)), [storageBuckets])
useEffect(() => {
// this is needed for tests
refreshBuckets()
}, [refreshBuckets])

const formik = useFormik({
initialValues:{
Expand Down Expand Up @@ -194,12 +202,13 @@ const BucketsPage = () => {
</TableRow>
</TableHead>
<TableBody>
{storageBuckets.map((bucket) =>
<BucketRow
bucket={bucket}
key={bucket.id}
/>
)}
{bucketsToShow
.map((bucket) =>
<BucketRow
bucket={bucket}
key={bucket.id}
/>
)}
</TableBody>
</Table>
<CustomModal
Expand Down
Loading