mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-29 20:01:26 +00:00
Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0df62b804 | ||
|
|
6803e62949 | ||
|
|
08f086daf3 | ||
|
|
293aa3e0ed | ||
|
|
faeb84da72 | ||
|
|
c30c9beb09 | ||
|
|
77f8c64d37 |
@@ -1,5 +1,7 @@
|
||||
name: Daily OFL OTA Update
|
||||
|
||||
run-name: Daily OFL OTA Update [OFL barrier]
|
||||
|
||||
# This workflow is intended for creating and publishing the OrcaFilamentLibrary (OFL) OPC package to
|
||||
# https://github.com/OrcaSlicer/orcaslicer-profiles, which generates an OTA update.
|
||||
# This cronjob runs daily at 00:00 UTC every day and scans main plus every release/vX.Y.Z branch for
|
||||
@@ -12,9 +14,9 @@ name: Daily OFL OTA Update
|
||||
# vendor-dispatch path is also what makes post_merge_profiles.yml call the OTA auto-publish API after
|
||||
# uploading - see post_merge_profiles.yml for both sides of that contract.
|
||||
#
|
||||
# At the start of each run, the pending-publish table is cleared up to a captured
|
||||
# timestamp (POST /api/v1/ota/ofl/pending/clear?timestamp=...). Changes merged after
|
||||
# that timestamp remain pending for the next run.
|
||||
# Each run captures a timestamp, dispatches the needed OFL publishers, waits for
|
||||
# all of them to finish, then clears the pending-publish table once. Changes merged
|
||||
# after that timestamp remain pending for the next run.
|
||||
|
||||
on:
|
||||
schedule:
|
||||
@@ -34,32 +36,14 @@ jobs:
|
||||
if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' }}
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Capture start timestamp and clear OFL pending queue
|
||||
- name: Capture start timestamp
|
||||
id: start
|
||||
shell: bash
|
||||
env:
|
||||
OTA_API_BASE_URL: ${{ vars.OTA_API_BASE_URL }}
|
||||
OTA_API_KEY: ${{ secrets.OFL_OTA_PUBLISH_KEY }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
[ -n "$OTA_API_BASE_URL" ] || { echo "::error::vars.OTA_API_BASE_URL is not set"; exit 1; }
|
||||
[ -n "$OTA_API_KEY" ] || { echo "::error::secrets.OFL_OTA_PUBLISH_KEY is not set"; exit 1; }
|
||||
|
||||
timestamp="$(date -u +%s)"
|
||||
echo "timestamp=$timestamp" >> "$GITHUB_OUTPUT"
|
||||
|
||||
resp_file="$RUNNER_TEMP/ota-pending-clear-response.json"
|
||||
status="$(curl -sS -o "$resp_file" -w '%{http_code}' -X POST \
|
||||
"${OTA_API_BASE_URL%/}/api/v1/ota/ofl/pending/clear?timestamp=$timestamp" \
|
||||
-H "Authorization: Bearer $OTA_API_KEY")"
|
||||
body="$(cat "$resp_file")"
|
||||
echo "$body"
|
||||
|
||||
if [ "$status" != "200" ]; then
|
||||
echo "::error::OTA pending-clear call failed with HTTP $status"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
@@ -84,27 +68,21 @@ jobs:
|
||||
| grep -E '^(main|release/v[0-9]+\.[0-9]+\.[0-9]+)$' | sort -u
|
||||
)
|
||||
|
||||
# The cron run is the checkpoint: a successful run means every
|
||||
# dispatched branch publisher completed and the pending queue was
|
||||
# cleared. Manual or push-triggered post_merge_profiles runs are not
|
||||
# checkpoints for this scan.
|
||||
successful_cron_runs="$(gh api --method GET \
|
||||
"repos/${{ github.repository }}/actions/workflows/ofl-ota-cronjob.yml/runs" \
|
||||
-f status=success -f branch=main -f per_page=100 --paginate \
|
||||
--jq '.workflow_runs[] | select((.display_title // "") | contains("[OFL barrier]"))')"
|
||||
since="$(jq -rs 'sort_by(.run_started_at) | last.run_started_at // empty' <<< "$successful_cron_runs")"
|
||||
|
||||
for branch in "${branches[@]}"; do
|
||||
echo "::group::$branch"
|
||||
|
||||
# post_merge_profiles.yml's own run history, not this workflow's: this
|
||||
# workflow only ever runs against main (schedule, or workflow_dispatch
|
||||
# --ref main), so its head branch never varies - filtering ITS history
|
||||
# by $branch would never match anything except main. post_merge_profiles.yml
|
||||
# genuinely runs per-branch (this dispatch below sets --ref "$branch"),
|
||||
# so its history is the real per-branch checkpoint. It also means a
|
||||
# failed publish naturally gets retried tomorrow: the checkpoint only
|
||||
# advances on a run that actually succeeded.
|
||||
# --method GET is required, not cosmetic: gh api defaults to POST
|
||||
# whenever -f fields are present unless a method is given
|
||||
# explicitly, and POST on this list-runs endpoint 404s - confirmed
|
||||
# on real Actions infrastructure, not just reasoned about.
|
||||
since="$(gh api --method GET "repos/${{ github.repository }}/actions/workflows/post_merge_profiles.yml/runs" \
|
||||
-f status=success -f branch="$branch" -f per_page=1 \
|
||||
--jq '.workflow_runs[0].run_started_at // empty')"
|
||||
|
||||
if [ -z "$since" ]; then
|
||||
echo "No prior successful run for $branch; checking OFL changes up to $SCAN_UNTIL."
|
||||
echo "No prior successful OFL cron run; checking $branch through $SCAN_UNTIL."
|
||||
changed_files="$(git log --until="$SCAN_UNTIL" --name-only --pretty=format: "origin/$branch" -- \
|
||||
resources/profiles/OrcaFilamentLibrary resources/profiles/OrcaFilamentLibrary.json \
|
||||
| sed '/^$/d')"
|
||||
@@ -124,16 +102,98 @@ jobs:
|
||||
fi
|
||||
|
||||
if [ "$changed" = true ]; then
|
||||
# Tolerate a per-branch failure (e.g. a pre-existing release branch
|
||||
# whose post_merge_profiles.yml predates the vendor/auto_publish
|
||||
# inputs) rather than aborting the whole scan under set -e.
|
||||
if ! gh workflow run post_merge_profiles.yml \
|
||||
dispatch_id="${GITHUB_RUN_ID}-${branch//\//-}"
|
||||
# Record successful dispatches for the barrier step below.
|
||||
# Branches whose workflow predates workflow_dispatch are skipped
|
||||
# with a warning, as they were before the barrier was added.
|
||||
if gh workflow run post_merge_profiles.yml \
|
||||
--repo "${{ github.repository }}" \
|
||||
--ref "$branch" \
|
||||
-f vendor="$VENDOR" -f auto_publish=true; then
|
||||
echo "::warning::failed to dispatch post_merge_profiles.yml for $branch - its post_merge_profiles.yml at this ref may predate the vendor/auto_publish inputs"
|
||||
-f vendor="$VENDOR" -f auto_publish=true \
|
||||
-f ofl_cron_dispatch_id="$dispatch_id"; then
|
||||
printf '%s\t%s\n' "$branch" "$dispatch_id" >> "$RUNNER_TEMP/ofl-dispatches.tsv"
|
||||
else
|
||||
echo "::warning::skipping $branch because post_merge_profiles.yml could not be dispatched at that ref"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "::endgroup::"
|
||||
done
|
||||
|
||||
- name: Wait for OFL publishers
|
||||
id: wait
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
DISPATCHES_FILE: ${{ runner.temp }}/ofl-dispatches.tsv
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [ ! -s "$DISPATCHES_FILE" ]; then
|
||||
echo "No OFL publisher workflows were dispatched; pending queue will not be cleared."
|
||||
echo "publishers_dispatched=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
: > "$RUNNER_TEMP/ofl-run-ids.tsv"
|
||||
while IFS=$'\t' read -r branch dispatch_id; do
|
||||
[ -n "$branch" ] || continue
|
||||
echo "Waiting for OFL publisher on $branch ($dispatch_id)"
|
||||
|
||||
run_id=""
|
||||
for _ in {1..120}; do
|
||||
runs_json="$(gh api --method GET \
|
||||
"repos/${{ github.repository }}/actions/workflows/post_merge_profiles.yml/runs" \
|
||||
-f branch="$branch" -f event=workflow_dispatch -f per_page=100)"
|
||||
run_id="$(jq -r --arg marker "[OFL cron $dispatch_id]" \
|
||||
'[.workflow_runs[] | select((.display_title // "") | contains($marker))] \
|
||||
| sort_by(.created_at) | last | .id // empty' <<< "$runs_json")"
|
||||
[ -n "$run_id" ] && break
|
||||
sleep 5
|
||||
done
|
||||
|
||||
if [ -z "$run_id" ]; then
|
||||
echo "::error::could not find dispatched post_merge_profiles run for $branch ($dispatch_id)"
|
||||
exit 1
|
||||
fi
|
||||
printf '%s\t%s\n' "$branch" "$run_id" >> "$RUNNER_TEMP/ofl-run-ids.tsv"
|
||||
done < "$DISPATCHES_FILE"
|
||||
|
||||
all_success=true
|
||||
while IFS=$'\t' read -r branch run_id; do
|
||||
[ -n "$run_id" ] || continue
|
||||
echo "Watching OFL publisher run $run_id for $branch"
|
||||
if ! gh run watch "$run_id" --repo "${{ github.repository }}" --exit-status; then
|
||||
all_success=false
|
||||
fi
|
||||
done < "$RUNNER_TEMP/ofl-run-ids.tsv"
|
||||
|
||||
if [ "$all_success" != true ]; then
|
||||
echo "::error::one or more OFL publisher workflows failed; pending queue will not be cleared"
|
||||
exit 1
|
||||
fi
|
||||
echo "publishers_dispatched=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Clear OFL pending queue
|
||||
if: steps.wait.outputs.publishers_dispatched == 'true'
|
||||
shell: bash
|
||||
env:
|
||||
OTA_API_BASE_URL: ${{ vars.OTA_API_BASE_URL }}
|
||||
OTA_API_KEY: ${{ secrets.OFL_OTA_PUBLISH_KEY }}
|
||||
TIMESTAMP: ${{ steps.start.outputs.timestamp }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
[ -n "$OTA_API_BASE_URL" ] || { echo "::error::vars.OTA_API_BASE_URL is not set"; exit 1; }
|
||||
[ -n "$OTA_API_KEY" ] || { echo "::error::secrets.OFL_OTA_PUBLISH_KEY is not set"; exit 1; }
|
||||
|
||||
resp_file="$RUNNER_TEMP/ota-pending-clear-response.json"
|
||||
status="$(curl -sS -o "$resp_file" -w '%{http_code}' -X POST \
|
||||
"${OTA_API_BASE_URL%/}/api/v1/ota/ofl/pending/clear?timestamp=$TIMESTAMP" \
|
||||
-H "Authorization: Bearer $OTA_API_KEY")"
|
||||
body="$(cat "$resp_file")"
|
||||
echo "$body"
|
||||
|
||||
if [ "$status" != "200" ]; then
|
||||
echo "::error::OTA pending-clear call failed with HTTP $status"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
name: Post-merge profiles
|
||||
|
||||
run-name: >-
|
||||
Post-merge profiles${{
|
||||
inputs.ofl_cron_dispatch_id != '' &&
|
||||
inputs.vendor == 'OrcaFilamentLibrary' &&
|
||||
(inputs.auto_publish == true || inputs.auto_publish == 'true') &&
|
||||
format(' [OFL cron {0}]', inputs.ofl_cron_dispatch_id) ||
|
||||
''
|
||||
}}
|
||||
|
||||
# Push-triggered counterpart to check_profiles.yml (which only gates PRs). When a
|
||||
# profile change lands on main or a release branch, rebuild the affected vendors'
|
||||
# binary preset caches (<vendor>.opc) and publish each as a versioned ZIP asset on
|
||||
@@ -59,6 +68,12 @@ on:
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
ofl_cron_dispatch_id:
|
||||
description: >-
|
||||
Unique marker supplied by the trusted OFL daily cron so it can find
|
||||
and wait for this dispatched workflow run.
|
||||
required: false
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
@@ -1443,6 +1443,7 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const
|
||||
libvgcode::EGCodeExtrusionRole::SupportTransition, libvgcode::EGCodeExtrusionRole::Mixed
|
||||
});
|
||||
m_paths_bounding_box = BoundingBoxf3(libvgcode::convert(bbox[0]).cast<double>(), libvgcode::convert(bbox[1]).cast<double>());
|
||||
m_max_bounding_box = m_paths_bounding_box;
|
||||
|
||||
if (wxGetApp().is_editor())
|
||||
m_contained_in_bed = wxGetApp().plater()->build_volume().all_paths_inside(gcode_result, m_paths_bounding_box);
|
||||
|
||||
@@ -818,7 +818,9 @@ int OrcaCloudServiceAgent::user_logout(bool request)
|
||||
}
|
||||
}
|
||||
|
||||
clear_session();
|
||||
// An explicit logout also wipes the backend the token storage option is not using, so a token
|
||||
// stranded by switching that option cannot sign the account back in later.
|
||||
clear_session(/*all_backends=*/request);
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -1603,7 +1605,9 @@ void OrcaCloudServiceAgent::persist_user_secret(const std::string& secret)
|
||||
}
|
||||
}
|
||||
|
||||
(void) stored;
|
||||
if (stored) {
|
||||
secret_stored = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool OrcaCloudServiceAgent::load_user_secret(std::string& out_secret)
|
||||
@@ -1643,6 +1647,7 @@ bool OrcaCloudServiceAgent::load_user_secret(std::string& out_secret)
|
||||
}
|
||||
|
||||
if (integrity_ok && aes256gcm_decrypt(encoded_payload, key, plain) && !plain.empty()) {
|
||||
secret_stored = true;
|
||||
out_secret = plain;
|
||||
// Upgrade legacy payloads to signed format
|
||||
if (payload.rfind("v2:", 0) != 0) {
|
||||
@@ -1660,6 +1665,7 @@ bool OrcaCloudServiceAgent::load_user_secret(std::string& out_secret)
|
||||
if (store.Load(SECRET_STORE_SERVICE, username, secret) && secret.IsOk()) {
|
||||
out_secret.assign(static_cast<const char*>(secret.GetData()), secret.GetSize());
|
||||
if (!out_secret.empty()) {
|
||||
secret_stored = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1669,12 +1675,21 @@ bool OrcaCloudServiceAgent::load_user_secret(std::string& out_secret)
|
||||
return false;
|
||||
}
|
||||
|
||||
void OrcaCloudServiceAgent::clear_user_secret()
|
||||
void OrcaCloudServiceAgent::clear_user_secret(bool all_backends)
|
||||
{
|
||||
// Nothing this process loaded or saved: leave the store alone. Deleting would only cost a
|
||||
// keychain round trip (or a hang while the keychain is unresponsive) and could remove a
|
||||
// login another instance just saved.
|
||||
if (!secret_stored.exchange(false) && !all_backends) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (all_backends || !m_use_encrypted_token_file) {
|
||||
wxSecretStore store = wxSecretStore::GetDefault();
|
||||
if (store.IsOk()) {
|
||||
store.Delete(SECRET_STORE_SERVICE);
|
||||
}
|
||||
}
|
||||
|
||||
compute_fallback_path();
|
||||
if (!secret_fallback_path.empty() && wxFileExists(wxString::FromUTF8(secret_fallback_path.c_str()))) {
|
||||
@@ -2022,13 +2037,13 @@ bool OrcaCloudServiceAgent::set_user_session(const json& session_json, bool noti
|
||||
return success;
|
||||
}
|
||||
|
||||
void OrcaCloudServiceAgent::clear_session()
|
||||
void OrcaCloudServiceAgent::clear_session(bool all_backends)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(session_mutex);
|
||||
session = SessionInfo{};
|
||||
}
|
||||
clear_user_secret();
|
||||
clear_user_secret(all_backends);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@@ -324,7 +324,7 @@ public:
|
||||
|
||||
void persist_user_secret(const std::string& secret);
|
||||
bool load_user_secret(std::string& out_secret);
|
||||
void clear_user_secret();
|
||||
void clear_user_secret(bool all_backends = false);
|
||||
|
||||
// Token refresh helpers
|
||||
bool refresh_if_expiring(std::chrono::seconds skew, const std::string& reason);
|
||||
@@ -342,7 +342,7 @@ public:
|
||||
bool persist = true);
|
||||
// Accepts either nested Orca cloud / GoTrue session JSON or flat WebView token JSON.
|
||||
bool set_user_session(const nlohmann::json& session_json, bool notify_login = true);
|
||||
void clear_session();
|
||||
void clear_session(bool all_backends = false);
|
||||
|
||||
static std::string generate_uuid_for_setting_id(const std::string& name, const std::string& user_id = "");
|
||||
|
||||
@@ -411,6 +411,11 @@ private:
|
||||
// Member variables - auth state
|
||||
PkceBundle pkce_bundle;
|
||||
std::string secret_fallback_path;
|
||||
// Set once this process has read a secret from the store or written one. Unless the user logs
|
||||
// out explicitly, clear_user_secret() only touches the store while it is set, so a logged-out
|
||||
// instance (the GUI polls the login status every 2 s) makes no keychain calls and cannot wipe
|
||||
// a login another instance saved.
|
||||
std::atomic_bool secret_stored{false};
|
||||
SessionHandler session_handler;
|
||||
OnLoginCompleteHandler on_login_complete_handler;
|
||||
SessionInfo session;
|
||||
|
||||
@@ -10,6 +10,7 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_prebuild_queue.cpp
|
||||
test_staged_build.cpp
|
||||
test_network_versions.cpp
|
||||
test_orca_cloud_agent.cpp
|
||||
test_action_source.cpp
|
||||
test_plugin_host_api.cpp
|
||||
test_plugin_capability_config.cpp
|
||||
|
||||
@@ -1,39 +1,6 @@
|
||||
#include <catch2/catch_all.hpp>
|
||||
|
||||
#include "slic3r/Utils/Http.hpp"
|
||||
#include "slic3r/Utils/OrcaCloudServiceAgent.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
nlohmann::json flat_session_json(const nlohmann::json& fields)
|
||||
{
|
||||
nlohmann::json session = {
|
||||
{"access_token", "test-token"},
|
||||
{"user_id", "test-user-id"}
|
||||
};
|
||||
session.update(fields);
|
||||
return session;
|
||||
}
|
||||
|
||||
nlohmann::json nested_session_json(const nlohmann::json& metadata)
|
||||
{
|
||||
return {
|
||||
{"access_token", "test-token"},
|
||||
{"user", {
|
||||
{"id", "test-user-id"},
|
||||
{"user_metadata", metadata}
|
||||
}}
|
||||
};
|
||||
}
|
||||
|
||||
std::string resolved_display_name(const nlohmann::json& session)
|
||||
{
|
||||
Slic3r::OrcaCloudServiceAgent agent("");
|
||||
REQUIRE(agent.set_user_session(session, false));
|
||||
return agent.get_user_nickname();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("Check SSL certificates paths", "[Http][NotWorking]") {
|
||||
|
||||
@@ -53,62 +20,6 @@ TEST_CASE("Check SSL certificates paths", "[Http][NotWorking]") {
|
||||
REQUIRE(status == 200);
|
||||
}
|
||||
|
||||
TEST_CASE("Orca cloud flat session resolves display name consistently", "[OrcaCloudServiceAgent]")
|
||||
{
|
||||
CHECK(resolved_display_name(flat_session_json({
|
||||
{"username", "orca_username"},
|
||||
{"display_name", "Display Name"},
|
||||
{"nickname", "Nickname"}
|
||||
})) == "Display Name");
|
||||
|
||||
CHECK(resolved_display_name(flat_session_json({
|
||||
{"username", "orca_username"},
|
||||
{"nickname", "Nickname"}
|
||||
})) == "Nickname");
|
||||
|
||||
CHECK(resolved_display_name(flat_session_json({
|
||||
{"username", "orca_username"},
|
||||
{"full_name", "Full Name"}
|
||||
})) == "Full Name");
|
||||
|
||||
CHECK(resolved_display_name(flat_session_json({
|
||||
{"username", "orca_username"},
|
||||
{"name", "Provider Name"}
|
||||
})) == "Provider Name");
|
||||
|
||||
CHECK(resolved_display_name(flat_session_json({
|
||||
{"username", "orca_username"}
|
||||
})) == "orca_username");
|
||||
}
|
||||
|
||||
TEST_CASE("Orca cloud nested session resolves display name consistently", "[OrcaCloudServiceAgent]")
|
||||
{
|
||||
CHECK(resolved_display_name(nested_session_json({
|
||||
{"username", "orca_username"},
|
||||
{"display_name", "Display Name"},
|
||||
{"nickname", "Nickname"}
|
||||
})) == "Display Name");
|
||||
|
||||
CHECK(resolved_display_name(nested_session_json({
|
||||
{"username", "orca_username"},
|
||||
{"nickname", "Nickname"}
|
||||
})) == "Nickname");
|
||||
|
||||
CHECK(resolved_display_name(nested_session_json({
|
||||
{"username", "orca_username"},
|
||||
{"full_name", "Full Name"}
|
||||
})) == "Full Name");
|
||||
|
||||
CHECK(resolved_display_name(nested_session_json({
|
||||
{"username", "orca_username"},
|
||||
{"name", "Provider Name"}
|
||||
})) == "Provider Name");
|
||||
|
||||
CHECK(resolved_display_name(nested_session_json({
|
||||
{"username", "orca_username"}
|
||||
})) == "orca_username");
|
||||
}
|
||||
|
||||
TEST_CASE("Http digest authentication", "[Http][NotWorking]") {
|
||||
Slic3r::Http g = Slic3r::Http::get("https://httpbingo.org/digest-auth/auth/guest/guest");
|
||||
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
#include <catch2/catch_all.hpp>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/filesystem/fstream.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "slic3r/Utils/OrcaCloudServiceAgent.hpp"
|
||||
#include "test_utils.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
namespace {
|
||||
|
||||
// The encrypted token file is the one secret backend a test can observe without a system
|
||||
// keychain. Every agent pointed at the same directory shares it, like separate app instances
|
||||
// share the keychain entry.
|
||||
std::unique_ptr<OrcaCloudServiceAgent> make_file_backed_agent(const fs::path& dir)
|
||||
{
|
||||
auto agent = std::make_unique<OrcaCloudServiceAgent>(dir.string());
|
||||
agent->set_use_encrypted_token_file(true);
|
||||
agent->set_config_dir(dir.string());
|
||||
return agent;
|
||||
}
|
||||
|
||||
fs::path secret_file(const fs::path& dir) { return dir / secret_constants::USER_SECRET_FILENAME; }
|
||||
|
||||
nlohmann::json flat_session_json(const nlohmann::json& fields)
|
||||
{
|
||||
nlohmann::json session = {
|
||||
{"access_token", "test-token"},
|
||||
{"user_id", "test-user-id"}
|
||||
};
|
||||
session.update(fields);
|
||||
return session;
|
||||
}
|
||||
|
||||
nlohmann::json nested_session_json(const nlohmann::json& metadata)
|
||||
{
|
||||
return {
|
||||
{"access_token", "test-token"},
|
||||
{"user", {
|
||||
{"id", "test-user-id"},
|
||||
{"user_metadata", metadata}
|
||||
}}
|
||||
};
|
||||
}
|
||||
|
||||
// set_user_session() persists the session, so it goes to a throwaway token file rather than the
|
||||
// system keychain of whoever runs the tests.
|
||||
std::string resolved_display_name(const nlohmann::json& session)
|
||||
{
|
||||
ScopedTemporaryDir dir("orca-secret");
|
||||
auto agent = make_file_backed_agent(dir.path());
|
||||
REQUIRE(agent->set_user_session(session, false));
|
||||
return agent->get_user_nickname();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("Logging out removes the secret this instance saved", "[OrcaCloudServiceAgent]")
|
||||
{
|
||||
ScopedTemporaryDir dir("orca-secret");
|
||||
auto agent = make_file_backed_agent(dir.path());
|
||||
|
||||
agent->persist_user_secret("refresh-token");
|
||||
REQUIRE(fs::exists(secret_file(dir.path())));
|
||||
|
||||
agent->user_logout(false);
|
||||
CHECK_FALSE(fs::exists(secret_file(dir.path())));
|
||||
}
|
||||
|
||||
TEST_CASE("Logging out removes a secret this instance loaded from the store", "[OrcaCloudServiceAgent]")
|
||||
{
|
||||
ScopedTemporaryDir dir("orca-secret");
|
||||
make_file_backed_agent(dir.path())->persist_user_secret("refresh-token");
|
||||
|
||||
auto agent = make_file_backed_agent(dir.path());
|
||||
std::string secret;
|
||||
REQUIRE(agent->load_user_secret(secret));
|
||||
CHECK(secret == "refresh-token");
|
||||
|
||||
agent->user_logout(false);
|
||||
CHECK_FALSE(fs::exists(secret_file(dir.path())));
|
||||
}
|
||||
|
||||
TEST_CASE("Logging out leaves a secret this instance never loaded or saved alone", "[OrcaCloudServiceAgent]")
|
||||
{
|
||||
ScopedTemporaryDir dir("orca-secret");
|
||||
make_file_backed_agent(dir.path())->persist_user_secret("refresh-token");
|
||||
|
||||
// A logged-out instance is asked to log out on every login-status poll.
|
||||
auto other = make_file_backed_agent(dir.path());
|
||||
other->user_logout(false);
|
||||
other->user_logout(false);
|
||||
CHECK(fs::exists(secret_file(dir.path())));
|
||||
|
||||
std::string secret;
|
||||
REQUIRE(make_file_backed_agent(dir.path())->load_user_secret(secret));
|
||||
CHECK(secret == "refresh-token");
|
||||
}
|
||||
|
||||
TEST_CASE("Logging out leaves a secret this instance could not read alone", "[OrcaCloudServiceAgent]")
|
||||
{
|
||||
ScopedTemporaryDir dir("orca-secret");
|
||||
// Written under another encryption key, e.g. by another OS user sharing the data directory.
|
||||
fs::ofstream(secret_file(dir.path())) << "v2:0000:not-a-payload-this-user-can-decrypt";
|
||||
|
||||
auto agent = make_file_backed_agent(dir.path());
|
||||
std::string secret;
|
||||
REQUIRE_FALSE(agent->load_user_secret(secret));
|
||||
|
||||
agent->user_logout(false);
|
||||
CHECK(fs::exists(secret_file(dir.path())));
|
||||
}
|
||||
|
||||
TEST_CASE("Orca cloud flat session resolves display name consistently", "[OrcaCloudServiceAgent]")
|
||||
{
|
||||
CHECK(resolved_display_name(flat_session_json({
|
||||
{"username", "orca_username"},
|
||||
{"display_name", "Display Name"},
|
||||
{"nickname", "Nickname"}
|
||||
})) == "Display Name");
|
||||
|
||||
CHECK(resolved_display_name(flat_session_json({
|
||||
{"username", "orca_username"},
|
||||
{"nickname", "Nickname"}
|
||||
})) == "Nickname");
|
||||
|
||||
CHECK(resolved_display_name(flat_session_json({
|
||||
{"username", "orca_username"},
|
||||
{"full_name", "Full Name"}
|
||||
})) == "Full Name");
|
||||
|
||||
CHECK(resolved_display_name(flat_session_json({
|
||||
{"username", "orca_username"},
|
||||
{"name", "Provider Name"}
|
||||
})) == "Provider Name");
|
||||
|
||||
CHECK(resolved_display_name(flat_session_json({
|
||||
{"username", "orca_username"}
|
||||
})) == "orca_username");
|
||||
}
|
||||
|
||||
TEST_CASE("Orca cloud nested session resolves display name consistently", "[OrcaCloudServiceAgent]")
|
||||
{
|
||||
CHECK(resolved_display_name(nested_session_json({
|
||||
{"username", "orca_username"},
|
||||
{"display_name", "Display Name"},
|
||||
{"nickname", "Nickname"}
|
||||
})) == "Display Name");
|
||||
|
||||
CHECK(resolved_display_name(nested_session_json({
|
||||
{"username", "orca_username"},
|
||||
{"nickname", "Nickname"}
|
||||
})) == "Nickname");
|
||||
|
||||
CHECK(resolved_display_name(nested_session_json({
|
||||
{"username", "orca_username"},
|
||||
{"full_name", "Full Name"}
|
||||
})) == "Full Name");
|
||||
|
||||
CHECK(resolved_display_name(nested_session_json({
|
||||
{"username", "orca_username"},
|
||||
{"name", "Provider Name"}
|
||||
})) == "Provider Name");
|
||||
|
||||
CHECK(resolved_display_name(nested_session_json({
|
||||
{"username", "orca_username"}
|
||||
})) == "orca_username");
|
||||
}
|
||||
Reference in New Issue
Block a user