Skip to content

Commit

Permalink
feat, fix: add api key handling, error with show-connections and use …
Browse files Browse the repository at this point in the history
…playwright for tests (#15)

* feat: add API key handling

* fix: bug with list connections

* feat: use plawright for tests

* feat: update python 3.9.1 to 3.9.19
  • Loading branch information
utkarsh-dixit committed Apr 11, 2024
1 parent 063cf9d commit 86f7382
Show file tree
Hide file tree
Showing 11 changed files with 176 additions and 73 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/sdk_cli_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
python-version: [3.11.6, 3.10.12, 3.12.3, 3.9.1]
python-version: [3.11.6, 3.10.12, 3.12.3, 3.9.19]
runs-on: ${{ matrix.os }}

steps:
Expand All @@ -28,7 +28,7 @@ jobs:
run: cd core && pip install -r requirements.txt

- name: Install dependencies
run: npm install
run: npm install -g pnpm && pnpm install

- name: Run tests
run: npm test
run: pnpm test
40 changes: 31 additions & 9 deletions core/composio/composio_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,14 +310,36 @@ def add_integration(args):

console.print(f"\n[green]> Adding integration: {integration_name.capitalize()}...[/green]\n")
try:
# @TODO: add logic to wait and ask for API_KEY
connection = client.initiate_connection("test-" + integration_name.lower() + "-connector")
webbrowser.open(connection.redirectUrl)
print(f"Please authenticate {integration_name} in the browser and come back here. URL: {connection.redirectUrl}")
spinner = Spinner(DOTS, f"[yellow]⚠[/yellow] Waiting for {integration_name} authentication...")
spinner.start()
connected_account = connection.wait_until_active()
spinner.stop()
app = client.sdk.get_app(args.integration_name)
auth_schemes = app.get('auth_schemes')
auth_schemes_arr = [auth_scheme.get('auth_mode') for auth_scheme in auth_schemes]
if len(auth_schemes_arr) > 1 and auth_schemes_arr[0] == 'API_KEY':
connection = client.initiate_connection("test-" + integration_name.lower() + "-connector")
fields = auth_schemes[0].get('fields')
fields_input = {}
for field in fields:
if field.get('expected_from_customer', True):
if field.get('required', False):
console.print(f"[green]> Enter {field.get('displayName', field.get('name'))}: [/green]", end="")
value = input() or field.get('default')
if not value: # If a required field is not provided and no default is available
console.print(f"[red]Error: {field.get('displayName', field.get('name'))} is required[/red]")
sys.exit(1)
else:
console.print(f"[green]> Enter {field.get('displayName', field.get('name'))} (Optional): [/green]", end="")
value = input() or field.get('default')
fields_input[field.get('name')] = value

connection.save_user_access_data(fields_input)
else:
# @TODO: add logic to wait and ask for API_KEY
connection = client.initiate_connection("test-" + integration_name.lower() + "-connector")
webbrowser.open(connection.redirectUrl)
print(f"Please authenticate {integration_name} in the browser and come back here. URL: {connection.redirectUrl}")
spinner = Spinner(DOTS, f"[yellow]⚠[/yellow] Waiting for {integration_name} authentication...")
spinner.start()
connected_account = connection.wait_until_active()
spinner.stop()
save_user_connection(connected_account.id, integration_name)
print("")
console.print(f"[green]✔[/green] {integration_name} added successfully!")
Expand All @@ -342,7 +364,7 @@ def list_connections(args):
appName = args.appName
console.print(f"\n[green]> Listing connections for: {appName}...[/green]\n")
try:
connections = client.get_list_of_connections(appName)
connections = client.get_list_of_connections([appName])
if connections:
for connection in connections:
console.print(f"[yellow]- {connection['integrationId']} ({connection['status']})[/yellow]")
Expand Down
3 changes: 2 additions & 1 deletion core/composio/sdk/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,8 @@ def get_list_of_connections(self, app_name: list[Union[App, str]] = None) -> lis
for i, item in enumerate(app_name):
if isinstance(item, App):
app_name[i] = item.value


resp = []
if app_name is not None:
resp = [item for item in resp if item.appUniqueId in app_name]

Expand Down
12 changes: 12 additions & 0 deletions core/composio/sdk/sdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ def __init__(self, sdk_instance: "Composio", **data):
super().__init__(**data)
self.sdk_instance = sdk_instance

def save_user_access_data(self, field_inputs: dict):
connected_account_id = self.sdk_instance.get_connected_account(self.connectedAccountId)
resp = self.sdk_instance.http_client.post(f"{self.sdk_instance.base_url}/v1/connectedAccounts", json={
"integrationId": connected_account_id.integrationId,
"data": field_inputs,
})
return resp.json()

def wait_until_active(
self, timeout=60
) -> "ConnectedAccount": # Timeout adjusted to seconds
Expand Down Expand Up @@ -248,6 +256,10 @@ def set_global_trigger(self, callback_url: str):
def get_list_of_apps(self):
resp = self.http_client.get(f"{self.base_url}/v1/apps")
return resp.json()

def get_app(self, app_name: str):
resp = self.http_client.get(f"{self.base_url}/v1/apps/{app_name}")
return resp.json()

def get_list_of_actions(
self, apps: list[App] = None, actions: list[Action] = None
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
"description": "1. Core - To access base APIs 2. Autogen - Use Composio tools with Autogen 3. CrewAI - Use Composio tools with CrewAI 4. Langchain - Use Composio tools with Langchain",
"main": "index.js",
"scripts": {
"test": "zx test/test.mjs"
"test": "playwright test"
},
"author": "",
"license": "ISC",
"dependencies": {
"@playwright/test": "^1.43.0",
"chalk": "^5.3.0",
"playwright": "^1.43.0",
"zx": "^8.0.1"
}
}
33 changes: 33 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
// Look for test files in the "tests" directory, relative to this configuration file.
testDir: 'tests',

// Run all tests in parallel.
fullyParallel: true,

// Fail the build on CI if you accidentally left test.only in the source code.
forbidOnly: !!process.env.CI,

// Retry on CI only.
retries: process.env.CI ? 2 : 0,

// Opt out of parallel tests on CI.
workers: process.env.CI ? 1 : undefined,
projects: [
{
name: 'user session management',
testMatch: /global\.setup\.ts/,
teardown: 'logout user session',
},
{
name: 'logout user session',
testMatch: /global\.teardown\.ts/,
},
{
name: 'cli',
dependencies: ['user session management'],
},
]
});
38 changes: 38 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

59 changes: 0 additions & 59 deletions test/test.mjs

This file was deleted.

25 changes: 25 additions & 0 deletions tests/global.setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { test as setup, expect } from '@playwright/test';
import { execSync } from 'child_process';
import fs from "fs";

const userDataPath = `${process.env.HOME}/.composio`;

setup('user session management', async ({ }) => {
// Check if directory exists, delete it if it does
if (fs.existsSync(userDataPath)) {
execSync(`rm -rf ${userDataPath}`);
console.log(`Existing directory '${userDataPath}' deleted successfully.`);
}

// Create directory and write file
execSync(`mkdir -p ${userDataPath}`);
fs.writeFileSync(
`${userDataPath}/user_data.json`,
JSON.stringify({ "api_key": "3kmtwhffkxvwebhnm7qwzj" }, null, 2)
);

// Read file and verify content
const data = fs.readFileSync(`${userDataPath}/user_data.json`, 'utf8');
expect(data).toContain('3kmtwhffkxvwebhnm7qwzj');
console.log('user_data.json created and verified successfully.');
});
8 changes: 8 additions & 0 deletions tests/global.teardown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { test as teardown, expect } from '@playwright/test';
import { execSync } from 'child_process';

teardown('logout user session', async ({ }) => {
const output = execSync(`python3 core/start_cli.py logout`).toString();
await new Promise((resolve) => setTimeout(resolve, 1000));
expect(output).not.toBeNull();
});
21 changes: 21 additions & 0 deletions tests/test.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { test, expect } from '@playwright/test';
import fs from 'fs';
import { execSync } from 'child_process';

test.describe('Python CLI Operations', () => {
const commands = [
{ command: 'whoami', description: 'Running whoami' },
{ command: 'show-apps', description: 'Running show-apps' },
{ command: 'show-connections github', description: 'Running show-connections github' },
{ command: 'list-triggers github', description: 'Running list-triggers github' },
];

commands.forEach(({ command, description }) => {
test(description, async () => {
const output = execSync(`python3 core/start_cli.py ${command}`).toString();
await new Promise((resolve) => setTimeout(resolve, 1000));
console.log(description + ':', output);
expect(output).not.toBeNull();
});
});
});

0 comments on commit 86f7382

Please sign in to comment.