Browse Source

feat: add JSON output files and improve test flow

- Add .generated directory for test artifacts
- Save key derivation data to key_derivation.json
- Save account initialization to account_init.json
- Save registration data to registration.json
- Save claim details to claim_details.json
- Save test environment to test-env.sh
- Save contacts data to contacts.json
- Add proper error handling for file operations
- Improve deeplink test flow with JSON-based data
- Add color output and better status messages
- Add ADB device detection and fallback to print mode

Technical Changes:
- Add file system operations with proper error handling
- Standardize JSON output format across Python/TypeScript
- Update test flow to use generated JSON files
- Add proper typing for registration response
- Improve error reporting and debug output

This improves the test workflow by saving all intermediate
data as JSON files that can be used by other test scripts.
The deeplink testing now uses this data instead of environment
variables for better reliability.
pull/127/head
Matthew Raymer 4 days ago
parent
commit
ad9b4836cd
  1. 108
      test-scripts/new_flow.py
  2. 163
      test-scripts/new_flow.ts
  3. 261
      test-scripts/run-deeplink-tests.sh

108
test-scripts/new_flow.py

@ -54,6 +54,7 @@ from jwcrypto import jwk
import asyncio
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec
from pathlib import Path
# Constants
DEFAULT_ROOT_DERIVATION_PATH = "m/84737769'/0'/0'/0'" # Custom derivation path for TimeSafari
@ -61,6 +62,16 @@ API_SERVER = "https://test-api.endorser.ch" # Endorser API endpoint
ENDORSER_DID = "did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F" # Keep original case
ENDORSER_PRIVATE_KEY = "2b6472c026ec2aa2c4235c994a63868fc9212d18b58f6cbfe861b52e71330f5b"
# Create .generated directory if it doesn't exist
GENERATED_DIR = Path('.generated')
def initialize_directories():
"""Create necessary directories for storing generated files."""
try:
GENERATED_DIR.mkdir(exist_ok=True)
except Exception as e:
print(f"Error creating .generated directory: {e}")
raise
def derive_address(
mnemonic: str,
@ -98,6 +109,21 @@ def derive_address(
print(f" Private Key: {private_hex[:8]}...")
print(f" Public Key: {public_hex[:8]}...")
# Save derivation data
derivation_data = {
"mnemonic": mnemonic,
"derivation_path": derivation_path,
"address": address,
"private_key": private_hex,
"public_key": public_hex
}
try:
GENERATED_DIR.mkdir(exist_ok=True)
with open(GENERATED_DIR / 'key_derivation.json', 'w') as f:
json.dump(derivation_data, f, indent=2)
except Exception as e:
print(f"Error saving derivation data: {e}")
return address, private_hex, public_hex, derivation_path
def new_identifier(
@ -165,13 +191,16 @@ def initialize_account() -> Dict[str, Any]:
# Create DID identifier
identity = new_identifier(address, public_hex, private_hex, derivation_path)
# Format account data
# Save account data
account_data = {
"did": identity["did"],
"identity": identity,
"mnemonic": mnemonic,
"derivation_path": derivation_path
}
with open(GENERATED_DIR / 'account_init.json', 'w') as f:
json.dump(account_data, f, indent=2)
print("\nAccount initialized:")
print(json.dumps(account_data, indent=2))
print()
@ -328,6 +357,15 @@ async def register(
response_data = response.json()
if response.status_code == 200 and response_data.get("success", {}).get("handleId"):
# Save registration data
registration_data = {
"active_did": active_did,
"jwt_token": jwt_token,
"response": response_data
}
with open(GENERATED_DIR / 'registration.json', 'w') as f:
json.dump(registration_data, f, indent=2)
return {
"success": True,
"handleId": response_data["success"]["handleId"],
@ -393,15 +431,69 @@ async def fetch_claim(
headers={
'Authorization': f'Bearer {auth_token}',
'Content-Type': 'application/json'
}
},
timeout=30 # 30 second timeout
)
return response.json()
claim_data = response.json()
# Save claim data
try:
with open(GENERATED_DIR / 'claim_details.json', 'w', encoding='utf-8') as f:
json.dump({
'claim_id': claim_id,
'active_did': active_did,
'response': claim_data
}, f, indent=2)
except Exception as e:
print(f"Error saving claim data: {e}")
return claim_data
except requests.RequestException as e:
print("\nError fetching claim:")
print(f"Error: {str(e)}")
# Save error state
try:
with open(
GENERATED_DIR / 'claim_details.json',
'w', encoding='utf-8') as f:
json.dump({
'claim_id': claim_id,
'active_did': active_did,
'error': str(e)
}, f, indent=2)
except Exception as write_err:
print(f"Error saving claim error: {write_err}")
raise
async def generate_test_env():
"""Generate test environment data for deeplink testing"""
test_env_data = {
'CONTACT1_DID': active_did,
'CONTACT1_KEY': private_key_hex,
'CONTACT2_DID': recipient_did,
'CONTACT2_KEY': recipient_key,
'ISSUER_DID': issuer_did,
'ISSUER_KEY': issuer_key,
'TEST_JWT': jwt_token,
'API_SERVER': API_SERVER
}
# Write test environment variables
with open(GENERATED_DIR / 'test-env.sh', 'w', encoding='utf-8') as f:
for key, value in test_env_data.items():
f.write(f'export {key}="{value}"\n')
# Write test contacts data
contacts_data = {
'contacts': [
{'did': active_did, 'name': 'Test Contact 1'},
{'did': recipient_did, 'name': 'Test Contact 2'}
]
}
with open(GENERATED_DIR / 'contacts.json', 'w', encoding='utf-8') as f:
json.dump(contacts_data, f, indent=2)
# Main execution
async def main():
"""
@ -442,6 +534,10 @@ async def main():
Registration result: {"success": true}
```
"""
# Initialize directories first
initialize_directories()
try:
# Step 1: Create a new DID
identity = initialize_account()
active_did = identity["did"]
@ -465,5 +561,11 @@ async def main():
except Exception as e:
print(f"Error fetching claim: {str(e)}")
# Step 4: Generate test environment data
await generate_test_env()
except Exception as e:
print(f"Error: {str(e)}")
if __name__ == "__main__":
asyncio.run(main())

163
test-scripts/new_flow.ts

@ -10,6 +10,8 @@ import { HDNode } from '@ethersproject/hdnode';
import { Wallet } from '@ethersproject/wallet';
import { createJWT, ES256KSigner, SimpleSigner } from 'did-jwt';
import axios from 'axios';
import { mkdir, writeFile } from 'fs/promises';
import { join } from 'path';
// Constants
const DEFAULT_ROOT_DERIVATION_PATH = "m/84737769'/0'/0'/0'";
@ -32,6 +34,19 @@ interface IIdentifier {
services: any[];
}
// Create .generated directory if it doesn't exist
const GENERATED_DIR = '.generated';
// Initialize directory creation
async function initializeDirectories() {
try {
await mkdir(GENERATED_DIR, { recursive: true });
} catch (err) {
console.error('Error creating .generated directory:', err);
throw err;
}
}
/**
* Generate a new mnemonic seed phrase
*/
@ -62,6 +77,24 @@ async function deriveAddress(
console.log(` Private Key: ${privateHex.substring(0,8)}...`);
console.log(` Public Key: ${publicHex.substring(0,8)}...`);
// Save derivation data
const derivationData = {
mnemonic,
derivationPath,
address,
privateKey: privateHex,
publicKey: publicHex
};
try {
await mkdir(GENERATED_DIR, { recursive: true });
await writeFile(
join(GENERATED_DIR, 'key_derivation.json'),
JSON.stringify(derivationData, null, 2)
);
} catch (err) {
console.error('Error saving derivation data:', err);
}
return [address, privateHex, publicHex, derivationPath];
}
@ -111,6 +144,12 @@ async function initializeAccount(): Promise<IIdentifier> {
console.log(JSON.stringify(accountData, null, 2));
console.log();
// Save account data
await writeFile(
join(GENERATED_DIR, 'account_init.json'),
JSON.stringify(accountData, null, 2)
);
return identity;
}
@ -210,6 +249,15 @@ async function register(
if (response.status === 200 && response.data.success?.handleId) {
// Return all success details
const registrationData = {
activeDid,
jwtToken,
response: response.data.success
};
await writeFile(
join(GENERATED_DIR, 'registration.json'),
JSON.stringify(registrationData, null, 2)
);
return {
success: true,
handleId: response.data.success.handleId,
@ -225,11 +273,29 @@ async function register(
// If we have success data but no handleId, it might be a partial success
if (response.data.success) {
const registrationData = {
activeDid,
jwtToken,
response: response.data.success
};
await writeFile(
join(GENERATED_DIR, 'registration.json'),
JSON.stringify(registrationData, null, 2)
);
return {
success: true,
...response.data.success
};
} else {
const registrationData = {
activeDid,
jwtToken,
response: response.data
};
await writeFile(
join(GENERATED_DIR, 'registration.json'),
JSON.stringify(registrationData, null, 2)
);
return { error: "Registration failed", details: response.data };
}
} catch (error: any) {
@ -247,6 +313,15 @@ async function register(
}
console.error();
const registrationData = {
activeDid,
jwtToken,
response: { error: `Error submitting claim: ${error.message}` }
};
await writeFile(
join(GENERATED_DIR, 'registration.json'),
JSON.stringify(registrationData, null, 2)
);
return { error: `Error submitting claim: ${error.message}` };
}
}
@ -284,7 +359,24 @@ async function fetchClaim(
}
);
return response.data;
const claimData = response.data;
// Save claim data
try {
await writeFile(
join(GENERATED_DIR, 'claim_details.json'),
JSON.stringify({
claim_id: claimId,
active_did: activeDid,
response: claimData
}, null, 2)
);
} catch (err) {
console.error('Error saving claim data:', err);
}
return claimData;
} catch (error: any) {
console.error("\nError fetching claim:");
if (error.response) {
@ -293,14 +385,77 @@ async function fetchClaim(
} else {
console.error(error.message);
}
// Save error state
try {
await writeFile(
join(GENERATED_DIR, 'claim_details.json'),
JSON.stringify({
claim_id: claimId,
active_did: activeDid,
error: error.message || 'Unknown error',
response: error.response?.data
}, null, 2)
);
} catch (writeErr) {
console.error('Error saving claim error:', writeErr);
}
throw error;
}
}
/**
* Generate test environment data for deeplink testing
*/
async function generateTestEnv(
identity: IIdentifier,
jwtToken: string,
apiServer: string = API_SERVER
): Promise<void> {
// Create test data structure
const testEnvData = {
CONTACT1_DID: identity.did,
CONTACT1_KEY: identity.keys[0].privateKeyHex,
CONTACT2_DID: `did:ethr:${Wallet.createRandom().address}`, // Generate random DID for contact 2
CONTACT2_KEY: Wallet.createRandom().privateKey.substring(2), // Remove '0x'
ISSUER_DID: ENDORSER_DID,
ISSUER_KEY: ENDORSER_PRIVATE_KEY,
TEST_JWT: jwtToken,
API_SERVER: apiServer
};
// Write test environment variables
const envContent = Object.entries(testEnvData)
.map(([key, value]) => `export ${key}="${value}"`)
.join('\n');
await writeFile(
join(GENERATED_DIR, 'test-env.sh'),
envContent + '\n',
'utf-8'
);
// Write test contacts data
const contactsData = {
contacts: [
{ did: testEnvData.CONTACT1_DID, name: 'Test Contact 1' },
{ did: testEnvData.CONTACT2_DID, name: 'Test Contact 2' }
]
};
await writeFile(
join(GENERATED_DIR, 'contacts.json'),
JSON.stringify(contactsData, null, 2),
'utf-8'
);
}
/**
* Main execution flow
*/
async function main() {
// Initialize directories first
await initializeDirectories();
try {
// Create a new DID
const identity = await initializeAccount();
@ -321,6 +476,9 @@ async function main() {
);
console.log("\nClaim Details:");
console.log(JSON.stringify(claimDetails, null, 2));
// Generate test environment data
await generateTestEnv(identity, result.claimId);
}
} catch (error: any) {
console.error("Error:", error.message);
@ -339,5 +497,6 @@ export {
initializeAccount,
createEndorserJwt,
register,
fetchClaim
fetchClaim,
generateTestEnv
};

261
test-scripts/run-deeplink-tests.sh

@ -1,175 +1,138 @@
#!/bin/bash
# File header documentation
# @file run-deeplink-tests.sh
# @brief Automated deeplink testing script using generated JSON files and ADB
# @author Matthew Raymer
# @date $(date +%Y-%m-%d)
# Color definitions
BLUE='\033[0;34m'
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Parse command line arguments
GENERATE_ONLY=false
ALL_TESTS=false
while getopts "ga" opt; do
TIMEOUT=5
TEST_MODE="execute" # Default to execute mode
while getopts "t:p" opt; do
case $opt in
g) GENERATE_ONLY=true ;;
a) ALL_TESTS=true ;;
t) TIMEOUT=$OPTARG ;;
p) TEST_MODE="print" ;;
\?) echo "Invalid option: -$OPTARG" >&2; exit 1 ;;
esac
done
# Directory for storing generated DIDs
mkdir -p .generated
# Verify .generated directory exists
if [ ! -d .generated ]; then
echo -e "${RED}Error: .generated directory not found${NC}"
echo "Please run the DID generation script first"
exit 1
fi
# Function to validate environment variables
validate_env() {
local missing=false
for var in CONTACT1_DID CONTACT1_KEY CONTACT2_DID CONTACT2_KEY ISSUER_DID ISSUER_KEY; do
if [ -z "${!var}" ]; then
echo "Error: $var is not set" >&2
missing=true
fi
done
if [ "$missing" = true ]; then
rm -f .generated/test-env.sh # Remove invalid env file
echo "Please regenerate DIDs by running:" >&2
echo "./test-scripts/run-deeplink-tests.sh -g" >&2
# Function to read and validate JSON files
validate_json_files() {
local required_files=("test-env.sh" "claim_details.json" "contacts.json")
for file in "${required_files[@]}"; do
if [ ! -f ".generated/$file" ]; then
echo -e "${RED}Error: Missing required file .generated/$file${NC}"
return 1
fi
done
return 0
}
# Check if we already have generated DIDs
if [ -f .generated/test-env.sh ] && [ "$GENERATE_ONLY" = false ]; then
echo "Using existing DIDs from .generated/test-env.sh"
source .generated/test-env.sh
validate_env || exit 1
else
# Function to extract DID info from did_generator.sh output
extract_did_info() {
local output="$1"
# Debug: Show what we received
echo "DEBUG: extract_did_info received: '$output'" >&2
# Parse and validate JSON
if ! printf '%s' "$output" | jq -e 'if type == "object" and .status == "success" then true else false end' >/dev/null 2>&1; then
echo "Error: DID generation failed" >&2
echo "Error details:" >&2
if ! printf '%s' "$output" | jq -e . >/dev/null 2>&1; then
echo "Invalid JSON output: $output" >&2
# Function to URL encode string
urlencode() {
local string="${1}"
local strlen=${#string}
local encoded=""
local pos c o
for (( pos=0 ; pos<strlen ; pos++ )); do
c=${string:$pos:1}
case "$c" in
[-_.~a-zA-Z0-9] ) o="${c}" ;;
* ) printf -v o '%%%02x' "'$c"
esac
encoded+="${o}"
done
echo "${encoded}"
}
# Function to execute or print deeplink
execute_deeplink() {
local url="$1"
local description="$2"
local encoded_url=$(urlencode "$url")
echo -e "\n${BLUE}Testing: $description${NC}"
if [ "$TEST_MODE" = "print" ]; then
echo -e "${YELLOW}Deep Link URL:${NC}"
echo "$url"
echo -e "${YELLOW}Encoded URL:${NC}"
echo "$encoded_url"
echo "---"
else
printf '%s' "$output" | jq -r '"\(.stage): \(.error // "Unknown error")"' >&2
fi
echo "URL: $url"
if adb shell am start -a android.intent.action.VIEW -d "$encoded_url"; then
echo -e "${GREEN}Success: Deeplink executed${NC}"
sleep "$TIMEOUT"
else
echo -e "${RED}Error: Failed to execute deeplink${NC}"
return 1
fi
fi
}
# Return the successful JSON
printf '%s' "$output"
}
echo "Generating first contact DID..."
# Debug: Show raw command output
DEBUG_OUTPUT=$(DEBUG=0 ./test-scripts/did_generator.sh)
GEN_STATUS=$?
echo "DEBUG: Raw did_generator.sh output: '$DEBUG_OUTPUT'" >&2
if [ $GEN_STATUS -ne 0 ]; then
echo "Error: did_generator.sh failed with status $GEN_STATUS" >&2
# Main test sequence
main() {
# Validate environment and files
if ! validate_json_files; then
exit 1
fi
CONTACT1_OUTPUT=$(printf '%s' "$DEBUG_OUTPUT" | tr -d '\r')
echo "DEBUG: After tr command: '$CONTACT1_OUTPUT'" >&2
# Source environment variables
source .generated/test-env.sh
# Debug: Show what we're passing to extract_did_info
echo "DEBUG: Calling extract_did_info with: '$CONTACT1_OUTPUT'" >&2
CONTACT1_INFO=$(extract_did_info "$CONTACT1_OUTPUT")
EXTRACT_STATUS=$?
echo "DEBUG: extract_did_info returned status: $EXTRACT_STATUS" >&2
echo "DEBUG: CONTACT1_INFO: '$CONTACT1_INFO'" >&2
# Load JSON data
CLAIM_DETAILS=$(cat .generated/claim_details.json)
CONTACTS=$(cat .generated/contacts.json)
if [ $EXTRACT_STATUS -ne 0 ]; then
echo "DEBUG: extract_did_info failed" >&2
exit 1
fi
CONTACT1_DID=$(printf '%s' "$CONTACT1_INFO" | jq -r .did)
CONTACT1_KEY=$(printf '%s' "$CONTACT1_INFO" | jq -r .privateKey)
echo "Generating second contact DID (Jordan)..."
DEBUG_OUTPUT=$(DEBUG=0 ./test-scripts/did_generator.sh)
CONTACT2_OUTPUT=$(printf '%s' "$DEBUG_OUTPUT" | tr -d '\r')
CONTACT2_INFO=$(extract_did_info "$CONTACT2_OUTPUT")
if [ $? -ne 0 ]; then
exit 1
fi
CONTACT2_DID=$(printf '%s' "$CONTACT2_INFO" | jq -r .did)
CONTACT2_KEY=$(printf '%s' "$CONTACT2_INFO" | jq -r .privateKey)
echo "Generating issuer DID..."
DEBUG_OUTPUT=$(DEBUG=0 ./test-scripts/did_generator.sh)
ISSUER_OUTPUT=$(printf '%s' "$DEBUG_OUTPUT" | tr -d '\r')
ISSUER_INFO=$(extract_did_info "$ISSUER_OUTPUT")
if [ $? -ne 0 ]; then
exit 1
fi
ISSUER_DID=$(printf '%s' "$ISSUER_INFO" | jq -r .did)
ISSUER_KEY=$(printf '%s' "$ISSUER_INFO" | jq -r .privateKey)
# Add some visual feedback about the generated DIDs
echo
echo "Generated DIDs:"
echo " Contact 1: ${CONTACT1_DID:0:20}..."
echo " Contact 2: ${CONTACT2_DID:0:20}..."
echo " Issuer: ${ISSUER_DID:0:20}..."
echo
# Create a temporary env file with the generated DIDs
echo "Creating test-env.sh with generated DIDs..."
cat > .generated/test-env.sh << EOF
#!/bin/bash
# Generated $(date)
export CONTACT1_DID="$CONTACT1_DID"
export CONTACT1_KEY="$CONTACT1_KEY"
export CONTACT2_DID="$CONTACT2_DID"
export CONTACT2_KEY="$CONTACT2_KEY"
export ISSUER_DID="$ISSUER_DID"
export ISSUER_KEY="$ISSUER_KEY"
EOF
# Debug output
echo "Contents of generated test-env.sh:"
cat .generated/test-env.sh
# Make sure file is executable
chmod +x .generated/test-env.sh
# Source and validate the newly created env file
echo "Sourcing generated test-env.sh..."
source .generated/test-env.sh
validate_env || exit 1
fi
# 1. Claim-based deeplinks
execute_deeplink "timesafari://claim-cert/$(jq -r .claim_id <<< "$CLAIM_DETAILS")" \
"Testing claim certificate view"
if [ "$GENERATE_ONLY" = true ]; then
echo "Generated DIDs and created environment file at .generated/test-env.sh"
echo "To use these DIDs, run:"
echo "source .generated/test-env.sh"
exit 0
fi
execute_deeplink "timesafari://claim-add-raw/$(jq -r .claim_id <<< "$CLAIM_DETAILS")" \
"Testing raw claim addition"
# 2. DID-based deeplinks
execute_deeplink "timesafari://did/$CONTACT1_DID" \
"Testing DID view"
execute_deeplink "timesafari://contact-edit/$CONTACT1_DID" \
"Testing contact editing"
# 3. JSON-based deeplinks
execute_deeplink "timesafari://contacts/import?contacts=$(jq -r @uri <<< "$CONTACTS")" \
"Testing contacts import"
# Create contacts JSON for deep links
CONTACTS_JSON=$(jq -n \
--arg did1 "$CONTACT1_DID" \
--arg did2 "$CONTACT2_DID" \
'[
{"did": $did1},
{
"did": $did2,
"name": "Jordan",
"nextPubKeyHashB64": "IBfRZfwdzeKOzqCx8b+WlLpMJHOAT9ZknIDJo7F3rZE=",
"publicKeyBase64": "A1eIndfaxgMpVwyD5dYe74DgjuIo5SwPZFCcLdOemjf"
}
]')
export CONTACTS_JSON
export TEST_MODE=${TEST_MODE:-print} # Use TEST_MODE from environment or default to print
# Run deep link tests in order
if [ "$ALL_TESTS" = true ]; then
./test-scripts/test-deeplinks.sh -t 5 -a
else
./test-scripts/test-deeplinks.sh -t 5
echo -e "\n${GREEN}All deeplink tests completed${NC}"
}
# Check for adb if not in print mode
if [ "$TEST_MODE" = "execute" ]; then
if ! command -v adb >/dev/null 2>&1; then
echo -e "${YELLOW}Warning: adb not found, switching to print mode${NC}"
TEST_MODE="print"
elif [ -z "$(adb devices | grep -v List | grep device)" ]; then
echo -e "${YELLOW}Warning: no devices/emulators found, switching to print mode${NC}"
TEST_MODE="print"
fi
fi
# Execute main test sequence
main
Loading…
Cancel
Save