diff --git a/test/docker/Dockerfile b/test/docker/Dockerfile index f4501fcb8..e6411aaf2 100644 --- a/test/docker/Dockerfile +++ b/test/docker/Dockerfile @@ -1,8 +1,16 @@ FROM ubuntu:jammy-20230425 +EXPOSE 9000 VOLUME ${PWD}/../:/publishing-client WORKDIR /publishing-client/test ARG platform -ENV platform $platform +ARG FUZZBUCKET_SSH_KEY +ARG FUZZBUCKET_URL +ARG FUZZBUCKET_CREDENTIALS + +ENV platform=$platform +ENV FUZZBUCKET_SSH_KEY=$FUZZBUCKET_SSH_KEY +ENV FUZZBUCKET_URL=$FUZZBUCKET_URL +ENV FUZZBUCKET_CREDENTIALS=$FUZZBUCKET_CREDENTIALS RUN export DEBIAN_FRONTEND=noninteractive && \ apt-get clean && \ @@ -24,14 +32,19 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ libasound2 \ libxtst6 \ xauth \ - xvfb + xvfb \ + python3 \ + python3-pip \ + python-is-python3 \ + jq -RUN export GOLANG_VERSION=1.20.4 \ +RUN export GOLANG_VERSION=1.21.0 \ && export GOLANG_DOWNLOAD_SHA256=698ef3243972a51ddb4028e4a1ac63dc6d60821bf18e59a807e051fee0a385bd \ && curl -fsSL "https://go.dev/dl/go${GOLANG_VERSION}.${platform}.tar.gz" -o golang.tar.gz \ && tar -C /usr/local -xzf golang.tar.gz \ && rm golang.tar.gz ENV PATH="$PATH:/usr/local/go/bin" +ENV PATH="$PATH:" RUN npm install -g n && \ n stable diff --git a/test/justfile b/test/justfile index cd25f902d..295bd2ac9 100644 --- a/test/justfile +++ b/test/justfile @@ -1,7 +1,7 @@ set shell := ["bash", "-uc"] export TARGET_SERVER := env_var_or_default("TARGET_SERVER", "http://localhost:3939") -# export API_KEY := env_var_or_default("PERFTEST_API_KEY", `echo -n admin | md5sum | cut -f1 -d" "`) + _tag := "rstudio/connect-client" build target: @@ -13,16 +13,46 @@ build target: fi docker build \ + --platform "${_platform}" \ + --build-arg BUILDKIT_INLINE_CACHE=1 \ + --build-arg platform={{target}} \ + --build-arg FUZZBUCKET_CREDENTIALS="$FUZZBUCKET_CREDENTIALS" \ + --build-arg FUZZBUCKET_URL="$FUZZBUCKET_URL" \ + --build-arg FUZZBUCKET_SSH_KEY="$FUZZBUCKET_SSH_KEY" \ + --pull \ + --tag {{_tag}}-{{target}}:latest \ + ./docker + +build-clean target: + #!/usr/bin/env bash + if [[ {{target}} =~ "linux-amd64" ]]; then + _platform="linux/amd64" + elif [[ {{target}} =~ "linux-arm64" ]]; then + _platform="linux/arm64" + fi + + docker build \ + --no-cache \ --platform "${_platform}" \ --build-arg BUILDKIT_INLINE_CACHE=1 \ --build-arg platform={{target}} \ --pull \ + --progress=plain \ --tag {{_tag}}-{{target}}:latest \ ./docker -init-connect: - docker compose pull connect && \ - docker compose up -d connect +init-connect target: + #!/usr/bin/env bash + pip install -r ./setup/requirements.txt + echo "${FUZZBUCKET_SSH_KEY}" > .fuzzbucket-ssh-key + chmod 600 .fuzzbucket-ssh-key + export SSH_OPTIONS="-i.fuzzbucket-ssh-key" + export CONNECT_VERSION=$(curl https://cdn.posit.co/connect/latest-packages.json | + jq ".packages[0].version") + export CONNECT_IP="$(python setup/connect-setup.py)" + ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no ubuntu@$CONNECT_IP "${SSH_OPTIONS}" sudo -E UNATTENDED=1 bash installer-ci.sh ${CONNECT_VERSION} + python setup/client-setup.py + just ../web/build-and-test-ci-e2e {{target}} run-client target: #!/usr/bin/env bash @@ -40,10 +70,8 @@ run-client target: -v "$(pwd)"/../:/publishing-client \ -w /publishing-client/ \ --rm {{_tag}}-{{target}} just test/_test-client {{target}} - elif [[ {{target}} =~ "windows" ]]; then - just _test-client windows-amd64 - elif [[ {{target}} =~ "macos" ]]; then - just _test-client darwin-amd64 + else + just _test-client {{target}} fi build-binary target: @@ -63,6 +91,7 @@ build-binary target: just build fi + local-ui-client target: #!/usr/bin/env bash if [[ {{target}} =~ "linux" ]]; then @@ -125,3 +154,20 @@ bats-install: git clone --depth=1 https://github.com/ztombol/bats-support.git ./libs/bats-support && \ git clone --depth=1 https://github.com/ztombol/bats-assert.git ./libs/bats-assert fi + +bash target: + #!/usr/bin/env bash + if [[ {{target}} =~ "linux-amd64" ]]; then + _platform="linux/amd64" + elif [[ {{target}} =~ "linux-arm64" ]]; then + _platform="linux/arm64" + fi + if [[ {{target}} =~ "linux" ]]; then + docker run -it \ + --platform "${_platform}" \ + -v "$(pwd)"/../:/publishing-client \ + -w /publishing-client/test \ + -e DOCKER="false" \ + --rm {{_tag}}-{{target}} \ + /bin/bash + fi \ No newline at end of file diff --git a/test/setup/client-setup.py b/test/setup/client-setup.py new file mode 100644 index 000000000..4ecb37168 --- /dev/null +++ b/test/setup/client-setup.py @@ -0,0 +1,72 @@ +import hashlib +import shutil +import os +import sys +from os.path import join + +server_txt = 'setup/servers.txt' +server_json = 'setup/servers.json' +shutil.copy(server_txt, server_json) + +def copyfile(server_txt, server_json): + # source_path = server_json # Replace with the path of the source file + destination_path = config_dirname() # Replace with the destination directory path + # desitination_file = os.path.join(destination_path, "servers.json") + print(destination_path) + # Copy the file to the destination + shutil.move(server_json, destination_path) + print("File copied successfully.") + +def config_dirname(platform=sys.platform, env=os.environ): + """Get the user's configuration directory path for this platform.""" + home = env.get("HOME", "~") + base_dir = home + + if platform.startswith("linux"): + base_dir = env.get("XDG_CONFIG_HOME", home) + elif platform == "darwin": + base_dir = join(home, "Library", "Application Support") + elif platform == "win32": + # noinspection SpellCheckingInspection + base_dir = env.get("APPDATA", home) + + if base_dir == home: + try: + os.makedirs(base_dir+"/.rsconnect-python/") + except OSError: + pass + base_dir=base_dir+"/.rsconnect-python" + return join(base_dir) + else: + os.makedirs(base_dir+"/rsconnect-python/") + base_dir=base_dir+"/rsconnect-python" + return join(base_dir) + +def replace_apikey(username): + # replace api key + with open(server_json, 'r') as file: + server = file.read() + api_key = server.replace('API_KEY', get_hash(username)) + with open(server_json, 'w') as file: + file.write(api_key) + file.close() + # replace connect url + with open(server_json, 'r') as file: + server = file.read() + connect_ip = server.replace('CONNECT_IP', os.environ['CONNECT_IP']) + with open(server_json, 'w') as file: + file.write(connect_ip) + file.close() + + # Open the file for writing and overwrite with the modified content + + + +def get_hash(username): + + # Calculate the MD5 hash for the username to get an API Key + md5_hash = hashlib.md5(username.encode()).hexdigest() + return md5_hash + +replace_apikey('admin') +copyfile(server_txt, server_json) \ No newline at end of file diff --git a/test/setup/connect-setup.py b/test/setup/connect-setup.py new file mode 100644 index 000000000..55a04f55f --- /dev/null +++ b/test/setup/connect-setup.py @@ -0,0 +1,49 @@ +import subprocess +import json +import requests +import time + +alias = "perftest-connect-20230518" +box_name = "connect-ci" +list_command = "fuzzbucket-client -j list" +create_command = "fuzzbucket-client create -c " + alias + " -n " + box_name +remove_command = "fuzzbucket-client rm " + box_name + +def check_existing_boxes(box_name): + output = subprocess.check_output(list_command, shell=True, text=True) + if "\"boxes\": {}" not in output: + boxes = json.loads(output) + connect_ip = boxes["boxes"][box_name]["public_ip"] + else: + subprocess.check_output(create_command, shell=True, text=True) + output = subprocess.check_output(list_command, shell=True, text=True) + boxes = json.loads(output) + time.sleep(5) + connect_ip = boxes["boxes"][box_name]["public_ip"] + return connect_ip + +def get_ip(box_name): + connect_ip = check_existing_boxes(box_name) + return connect_ip + +def connect_ready(box_name, max_attempts, interval): + connect_box=get_ip(box_name) + attempts = 0 + while attempts < max_attempts: + try: + response = requests.get("http://"+connect_box+":3939/__ping__") + if response.status_code == 200: + return response.text + except requests.RequestException: + pass + + time.sleep(interval) + attempts += 1 + return None + +response = connect_ready(box_name, 20, 5) + +if response: + print(get_ip(box_name)) +else: + print("Server did not respond after multiple attempts.") \ No newline at end of file diff --git a/test/setup/requirements.txt b/test/setup/requirements.txt new file mode 100644 index 000000000..f84fa141a --- /dev/null +++ b/test/setup/requirements.txt @@ -0,0 +1,2 @@ +requests +fuzzbucket-client \ No newline at end of file diff --git a/test/setup/servers.txt b/test/setup/servers.txt new file mode 100644 index 000000000..a875b367a --- /dev/null +++ b/test/setup/servers.txt @@ -0,0 +1,9 @@ +{ + "connect": { + "name": "connect", + "url": "http://CONNECT_IP:3939", + "api_key": "API_KEY", + "insecure": false, + "ca_cert": null + } +} \ No newline at end of file diff --git a/web/cypress/e2e/home.cy.ts b/web/cypress/e2e/home.cy.ts index aaf9a0d0e..26a340f04 100644 --- a/web/cypress/e2e/home.cy.ts +++ b/web/cypress/e2e/home.cy.ts @@ -23,3 +23,27 @@ describe('Check Files', () => { cy.get('.q-tree__node-header-content').contains('fastapi-simple'); }); }); +describe('Publish', () => { + it('hit the publish button', () => { + cy.visit('http://127.0.0.1:9000/?token=abc123'); + cy.get('.block').contains('Publish') + .click(); + }); +}); + +describe('Check Connect Deployment', () => { + it('check deployment', () => { + cy.visit(Cypress.env('CYPRESS_CONNECT_ADDRESS')); + cy.get('#username').type('admin'); + cy.get('#password').type('password'); + cy.get('button[data-automation="login-panel-submit"]') + .click(); + cy.get('#rs_radio_cop-visibility_editor') + .click(); + cy.get('h1[data-automation="content-list-title"]') + .contains('Your Content'); + cy.get('td[data-automation="content-row-icon-title-cell"]') + .contains('Untitled') + .click(); + }); +});