clickup_v2 0.1.1

A comprehensive Rust client library and CLI for ClickUp API v2 with OAuth2 authentication, task management, and custom fields support
Documentation
use mockito::{mock, Mock, Matcher};
use serde_json::{json, Value};

/// Mock para simular respostas de sucesso da API
pub struct ApiMockBuilder {
    method: String,
    path: String,
    status: usize,
    response_body: Option<Value>,
    headers: Vec<(String, String)>,
}

impl ApiMockBuilder {
    /// Cria um novo mock builder
    pub fn new(method: &str, path: &str) -> Self {
        Self {
            method: method.to_string(),
            path: path.to_string(),
            status: 200,
            response_body: None,
            headers: vec![("content-type".to_string(), "application/json".to_string())],
        }
    }

    /// Define o status HTTP da resposta
    pub fn with_status(mut self, status: usize) -> Self {
        self.status = status;
        self
    }

    /// Define o corpo da resposta
    pub fn with_body(mut self, body: Value) -> Self {
        self.response_body = Some(body);
        self
    }

    /// Adiciona um header à resposta
    pub fn with_header(mut self, key: &str, value: &str) -> Self {
        self.headers.push((key.to_string(), value.to_string()));
        self
    }

    /// Constrói e cria o mock
    pub fn build(self) -> Mock {
        let mut mock = mock(self.method.as_str(), self.path.as_str())
            .with_status(self.status);

        for (key, value) in self.headers {
            mock = mock.with_header(key.as_str(), value.as_str());
        }

        if let Some(body) = self.response_body {
            mock = mock.with_body(body.to_string());
        }

        mock.create()
    }
}

/// Cria mocks para usuário autenticado
pub fn mock_authenticated_user() -> Mock {
    ApiMockBuilder::new("GET", "/user")
        .with_body(json!({
            "user": {
                "id": 12345,
                "username": "test_user",
                "email": "test@example.com",
                "color": "#FF0000",
                "profilePicture": null,
                "initials": "TU",
                "week_start_day": 1,
                "global_font_support": false,
                "timezone": "UTC"
            }
        }))
        .build()
}

/// Cria mock para times autorizados
pub fn mock_authorized_teams() -> Mock {
    ApiMockBuilder::new("GET", "/team")
        .with_body(json!({
            "teams": [
                {
                    "id": "123",
                    "name": "Test Team",
                    "color": "#FF0000",
                    "avatar": null,
                    "members": []
                }
            ]
        }))
        .build()
}

/// Cria mock para erro de autenticação
pub fn mock_unauthorized_error() -> Mock {
    ApiMockBuilder::new("GET", "/user")
        .with_status(401)
        .with_body(json!({
            "err": "Token is invalid",
            "ECODE": "OAUTH_021"
        }))
        .build()
}

/// Cria mock para erro de rate limit
pub fn mock_rate_limit_error(path: &str) -> Mock {
    ApiMockBuilder::new("GET", path)
        .with_status(429)
        .with_header("x-ratelimit-limit", "100")
        .with_header("x-ratelimit-remaining", "0")
        .with_header("x-ratelimit-reset", "1567780450")
        .with_body(json!({
            "err": "Rate limit exceeded",
            "ECODE": "RATE_LIMIT"
        }))
        .build()
}

/// Cria mock para erro interno do servidor
pub fn mock_internal_server_error(path: &str) -> Mock {
    ApiMockBuilder::new("GET", path)
        .with_status(500)
        .with_body(json!({
            "err": "Internal server error",
            "ECODE": "SERVER_ERROR"
        }))
        .build()
}

/// Cria mock para lista de espaços
pub fn mock_spaces(team_id: &str) -> Mock {
    ApiMockBuilder::new("GET", format!("/team/{}/space", team_id).as_str())
        .with_body(json!({
            "spaces": [
                {
                    "id": "789",
                    "name": "Test Space",
                    "private": false,
                    "statuses": [
                        {
                            "id": "open",
                            "status": "Open",
                            "type": "open",
                            "orderindex": 0,
                            "color": "#d3d3d3"
                        }
                    ]
                }
            ]
        }))
        .build()
}

/// Cria mock para lista de pastas
pub fn mock_folders(space_id: &str) -> Mock {
    ApiMockBuilder::new("GET", format!("/space/{}/folder", space_id).as_str())
        .with_body(json!({
            "folders": [
                {
                    "id": "111",
                    "name": "Test Folder",
                    "orderindex": 0,
                    "override_statuses": false,
                    "hidden": false
                }
            ]
        }))
        .build()
}

/// Cria mock para lista de listas
pub fn mock_lists(folder_id: &str) -> Mock {
    ApiMockBuilder::new("GET", format!("/folder/{}/list", folder_id).as_str())
        .with_body(json!({
            "lists": [
                {
                    "id": "222",
                    "name": "Test List",
                    "orderindex": 1,
                    "status": {
                        "status": "active",
                        "color": "#00FF00"
                    }
                }
            ]
        }))
        .build()
}

/// Cria mock para tarefas
pub fn mock_tasks(list_id: &str) -> Mock {
    ApiMockBuilder::new("GET", format!("/list/{}/task", list_id).as_str())
        .with_body(json!({
            "tasks": [
                {
                    "id": "task123",
                    "name": "Test Task",
                    "description": "Task description",
                    "status": {
                        "id": "open",
                        "status": "Open",
                        "color": "#d3d3d3"
                    },
                    "assignees": [],
                    "tags": [],
                    "priority": null,
                    "due_date": null,
                    "archived": false
                }
            ]
        }))
        .build()
}

/// Cria mock para criação de tarefa
pub fn mock_create_task(list_id: &str, expected_body: Value) -> Mock {
    mock("POST", format!("/list/{}/task", list_id).as_str())
        .match_header("authorization", "Bearer test_token")
        .match_header("content-type", "application/json")
        .match_body(Matcher::Json(expected_body))
        .with_status(200)
        .with_header("content-type", "application/json")
        .with_body(json!({
            "id": "new_task_id",
            "name": "New Task",
            "status": {
                "id": "open",
                "status": "Open"
            }
        }).to_string())
        .create()
}

/// Cria mock para atualização de tarefa
pub fn mock_update_task(task_id: &str, expected_body: Value) -> Mock {
    mock("PUT", format!("/task/{}", task_id).as_str())
        .match_header("authorization", "Bearer test_token")
        .match_header("content-type", "application/json")
        .match_body(Matcher::Json(expected_body))
        .with_status(200)
        .with_header("content-type", "application/json")
        .with_body(json!({
            "id": task_id,
            "name": "Updated Task",
            "status": {
                "id": "complete",
                "status": "Complete"
            }
        }).to_string())
        .create()
}

/// Cria mock para exclusão de tarefa
pub fn mock_delete_task(task_id: &str) -> Mock {
    mock("DELETE", format!("/task/{}", task_id).as_str())
        .match_header("authorization", "Bearer test_token")
        .with_status(204)
        .create()
}

/// Helper para criar múltiplos mocks de uma vez
pub struct MockServer {
    mocks: Vec<Mock>,
}

impl MockServer {
    pub fn new() -> Self {
        Self { mocks: Vec::new() }
    }

    pub fn add_mock(mut self, mock: Mock) -> Self {
        self.mocks.push(mock);
        self
    }

    pub fn with_authenticated_user(self) -> Self {
        self.add_mock(mock_authenticated_user())
    }

    pub fn with_teams(self) -> Self {
        self.add_mock(mock_authorized_teams())
    }

    pub fn with_spaces(self, team_id: &str) -> Self {
        self.add_mock(mock_spaces(team_id))
    }

    pub fn with_folders(self, space_id: &str) -> Self {
        self.add_mock(mock_folders(space_id))
    }

    pub fn with_lists(self, folder_id: &str) -> Self {
        self.add_mock(mock_lists(folder_id))
    }

    pub fn with_tasks(self, list_id: &str) -> Self {
        self.add_mock(mock_tasks(list_id))
    }

    pub fn build(self) -> Vec<Mock> {
        self.mocks
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_api_mock_builder() {
        let _mock = ApiMockBuilder::new("GET", "/test")
            .with_status(201)
            .with_header("x-custom", "value")
            .with_body(json!({ "test": "data" }))
            .build();
    }

    #[test]
    fn test_mock_server_builder() {
        let _mocks = MockServer::new()
            .with_authenticated_user()
            .with_teams()
            .with_spaces("123")
            .build();

        assert!(!_mocks.is_empty());
    }
}