pinecone_sdk/pinecone/
control.rs

1use std::cmp::min;
2use std::time::Duration;
3
4use crate::openapi::apis::manage_indexes_api;
5use crate::openapi::models::CreateIndexRequest;
6use crate::pinecone::PineconeClient;
7use crate::utils::errors::PineconeError;
8
9use crate::models::{
10    Cloud, CollectionList, CollectionModel, ConfigureIndexRequest, ConfigureIndexRequestSpec,
11    ConfigureIndexRequestSpecPod, CreateCollectionRequest, DeletionProtection, IndexList,
12    IndexModel, IndexSpec, Metric, PodSpec, PodSpecMetadataConfig, ServerlessSpec, WaitPolicy,
13};
14
15impl PineconeClient {
16    /// Creates a serverless index.
17    ///
18    /// ### Arguments
19    /// * `name: &str` - Name of the index to create.
20    /// * `dimension: i32` - Dimension of the vectors to be inserted in the index.
21    /// * `metric: Metric` - The distance metric to be used for similarity search.
22    /// * `cloud: Cloud` - The public cloud where you would like your index hosted.
23    /// * `region: &str` - The region where you would like your index to be created.
24    /// * `deletion_protection: DeletionProtection` - Deletion protection for the index.
25    /// * `timeout: WaitPolicy` - The wait policy for index creation. If the index becomes ready before the specified duration, the function will return early. If the index is not ready after the specified duration, the function will return an error.
26    ///
27    /// ### Return
28    /// * `Result<IndexModel, PineconeError>`
29    ///
30    /// ### Example
31    /// ```no_run
32    /// use pinecone_sdk::models::{IndexModel, Metric, Cloud, WaitPolicy, DeletionProtection};
33    /// use pinecone_sdk::utils::errors::PineconeError;
34    ///
35    /// # #[tokio::main]
36    /// # async fn main() -> Result<(), PineconeError>{
37    /// let pinecone = pinecone_sdk::pinecone::default_client()?;
38    ///
39    /// // Create an index.
40    /// let response: Result<IndexModel, PineconeError> = pinecone.create_serverless_index(
41    ///     "index-name", // Name of the index
42    ///     10, // Dimension of the vectors
43    ///     Metric::Cosine, // Distance metric
44    ///     Cloud::Aws, // Cloud provider
45    ///     "us-east-1", // Region
46    ///     DeletionProtection::Enabled, // Deletion protection
47    ///     WaitPolicy::NoWait // Timeout
48    /// ).await;
49    ///
50    /// # Ok(())
51    /// # }
52    /// ```
53    pub async fn create_serverless_index(
54        &self,
55        name: &str,
56        dimension: i32,
57        metric: Metric,
58        cloud: Cloud,
59        region: &str,
60        deletion_protection: DeletionProtection,
61        timeout: WaitPolicy,
62    ) -> Result<IndexModel, PineconeError> {
63        // create request specs
64        let create_index_request_spec = IndexSpec {
65            serverless: Some(Box::new(ServerlessSpec {
66                cloud,
67                region: region.to_string(),
68            })),
69            pod: None,
70        };
71
72        let create_index_request = CreateIndexRequest {
73            name: name.to_string(),
74            dimension,
75            deletion_protection: Some(deletion_protection),
76            metric: Some(metric.into()),
77            spec: Some(Box::new(create_index_request_spec)),
78        };
79
80        // make openAPI call
81        let res = manage_indexes_api::create_index(&self.openapi_config, create_index_request)
82            .await
83            .map_err(|e| PineconeError::from(e))?;
84
85        // poll index status
86        match self.handle_poll_index(name, timeout).await {
87            Ok(_) => Ok(res.into()),
88            Err(e) => Err(e),
89        }
90    }
91
92    /// Creates a pod index.
93    ///
94    /// ### Arguments
95    /// * `name: &str` - The name of the index
96    /// * `dimension: i32` - The dimension of the index
97    /// * `metric: Metric` - The metric to use for the index
98    /// * `environment: &str` - The environment where the pod index will be deployed. Example: 'us-east1-gcp'
99    /// * `pod_type: &str` - This value combines pod type and pod size into a single string. This configuration is your main lever for vertical scaling.
100    /// * `pods: i32` - The number of pods to deploy.
101    /// * `replicas: i32` - The number of replicas to deploy for the pod index.
102    /// * `shards: i32` - The number of shards to use. Shards are used to expand the amount of vectors you can store beyond the capacity of a single pod.
103    /// * `deletion_protection: DeletionProtection` - Deletion protection for the index.
104    /// * `metadata_indexed: Option<&[&str]>` - The metadata fields to index.
105    /// * `source_collection: Option<&str>` - The name of the collection to use as the source for the pod index. This configuration is only used when creating a pod index from an existing collection.
106    /// * `timeout: WaitPolicy` - The wait policy for index creation. If the index becomes ready before the specified duration, the function will return early. If the index is not ready after the specified duration, the function will return an error.
107    ///
108    /// ### Return
109    /// * Result<IndexModel, PineconeError>`
110    ///
111    /// ### Example
112    /// ```no_run
113    /// use pinecone_sdk::models::{IndexModel, Metric, Cloud, WaitPolicy, DeletionProtection};
114    /// use pinecone_sdk::utils::errors::PineconeError;
115    /// use std::time::Duration;
116    ///
117    /// # #[tokio::main]
118    /// # async fn main() -> Result<(), PineconeError> {
119    /// let pinecone = pinecone_sdk::pinecone::default_client()?;
120    ///
121    /// // Create a pod index.
122    /// let response: Result<IndexModel, PineconeError> = pinecone.create_pod_index(
123    ///     "index_name", // Name of the index
124    ///     10, // Dimension of the index
125    ///     Metric::Cosine, // Distance metric
126    ///     "us-east-1", // Environment
127    ///     "p1.x1", // Pod type
128    ///     1, // Number of pods
129    ///     1, // Number of replicas
130    ///     1, // Number of shards
131    ///     DeletionProtection::Enabled, // Deletion protection
132    ///     Some( // Metadata fields to index
133    ///         &vec!["genre",
134    ///         "title",
135    ///         "imdb_rating"]),
136    ///     Some("example-collection"), // Source collection
137    ///     WaitPolicy::WaitFor(Duration::from_secs(10)), // Timeout
138    /// )
139    /// .await;
140    /// # Ok(())
141    /// # }
142    /// ```
143    pub async fn create_pod_index(
144        &self,
145        name: &str,
146        dimension: i32,
147        metric: Metric,
148        environment: &str,
149        pod_type: &str,
150        pods: i32,
151        replicas: i32,
152        shards: i32,
153        deletion_protection: DeletionProtection,
154        metadata_indexed: Option<&[&str]>,
155        source_collection: Option<&str>,
156        timeout: WaitPolicy,
157    ) -> Result<IndexModel, PineconeError> {
158        // create request specs
159        let indexed = metadata_indexed.map(|i| i.iter().map(|s| s.to_string()).collect());
160
161        let pod_spec = PodSpec {
162            environment: environment.to_string(),
163            replicas,
164            shards,
165            pod_type: pod_type.to_string(),
166            pods,
167            metadata_config: Some(Box::new(PodSpecMetadataConfig { indexed })),
168            source_collection: source_collection.map(|s| s.to_string()),
169        };
170
171        let spec = IndexSpec {
172            serverless: None,
173            pod: Some(Box::new(pod_spec)),
174        };
175
176        let create_index_request = CreateIndexRequest {
177            name: name.to_string(),
178            dimension,
179            deletion_protection: Some(deletion_protection),
180            metric: Some(metric.into()),
181            spec: Some(Box::new(spec)),
182        };
183
184        // make openAPI call
185        let res = manage_indexes_api::create_index(&self.openapi_config, create_index_request)
186            .await
187            .map_err(|e| PineconeError::from(e))?;
188
189        // poll index status
190        match self.handle_poll_index(name, timeout).await {
191            Ok(_) => Ok(res.into()),
192            Err(e) => Err(e),
193        }
194    }
195
196    // Checks if the index is ready by polling the index status
197    async fn handle_poll_index(
198        &self,
199        name: &str,
200        timeout: WaitPolicy,
201    ) -> Result<(), PineconeError> {
202        match timeout {
203            WaitPolicy::WaitFor(duration) => {
204                let start_time = std::time::Instant::now();
205
206                loop {
207                    // poll index status, if ready return early
208                    if self.is_ready(name).await {
209                        break;
210                    }
211
212                    match duration.cmp(&start_time.elapsed()) {
213                        // if index not ready after waiting specified duration, return error
214                        std::cmp::Ordering::Less => {
215                            let message = format!("Index \"{name}\" not ready");
216                            return Err(PineconeError::TimeoutError { message });
217                        }
218                        // if still waiting, sleep for 5 seconds or remaining time
219                        std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => {
220                            let time_remaining = duration.saturating_sub(start_time.elapsed());
221                            tokio::time::sleep(Duration::from_millis(min(
222                                time_remaining.as_millis() as u64,
223                                5000,
224                            )))
225                            .await;
226                        }
227                    }
228                }
229            }
230            WaitPolicy::NoWait => {}
231        }
232
233        Ok(())
234    }
235
236    // Gets ready status of an index
237    async fn is_ready(&self, name: &str) -> bool {
238        let res = manage_indexes_api::describe_index(&self.openapi_config, name).await;
239        match res {
240            Ok(index) => index.status.ready,
241            Err(_) => false,
242        }
243    }
244
245    /// Describes an index.
246    ///
247    /// ### Arguments
248    /// * `name: &str` - Name of the index to describe.
249    ///
250    /// ### Return
251    /// * `Result<IndexModel, PineconeError>`
252    ///
253    /// ### Example
254    /// ```no_run
255    /// use pinecone_sdk::models::IndexModel;
256    /// use pinecone_sdk::utils::errors::PineconeError;
257    ///
258    /// # #[tokio::main]
259    /// # async fn main() -> Result<(), PineconeError>{
260    /// let pinecone = pinecone_sdk::pinecone::default_client()?;
261    ///
262    /// // Describe an index in the project.
263    /// let response: Result<IndexModel, PineconeError> = pinecone.describe_index("index-name").await;
264    /// # Ok(())
265    /// # }
266    /// ```
267    pub async fn describe_index(&self, name: &str) -> Result<IndexModel, PineconeError> {
268        // make openAPI call
269        let res = manage_indexes_api::describe_index(&self.openapi_config, name)
270            .await
271            .map_err(|e| PineconeError::from(e))?;
272
273        Ok(res.into())
274    }
275
276    /// Lists all indexes.
277    ///
278    /// The results include a description of all indexes in your project, including the
279    /// index name, dimension, metric, status, and spec.
280    ///
281    /// ### Return
282    /// * `Result<IndexList, PineconeError>`
283    ///
284    /// ### Example
285    /// ```no_run
286    /// use pinecone_sdk::models::IndexList;
287    /// use pinecone_sdk::utils::errors::PineconeError;
288    ///
289    /// # #[tokio::main]
290    /// # async fn main() -> Result<(), PineconeError>{
291    /// let pinecone = pinecone_sdk::pinecone::default_client()?;
292    ///
293    /// // List all indexes in the project.
294    /// let response: Result<IndexList, PineconeError> = pinecone.list_indexes().await;
295    /// # Ok(())
296    /// # }
297    /// ```
298    pub async fn list_indexes(&self) -> Result<IndexList, PineconeError> {
299        // make openAPI call
300        let res = manage_indexes_api::list_indexes(&self.openapi_config)
301            .await
302            .map_err(|e| PineconeError::from(e))?;
303
304        Ok(res.into())
305    }
306
307    /// Configures an index.
308    ///
309    /// This operation changes the deletion protection specification, the pod type, and the number of replicas for an index.
310    /// Deletion protection can be changed for both pod and serverless indexes, while pod types and number of replicas can only be changed for pod indexes.
311    ///
312    /// ### Arguments
313    /// * name: &str - The name of the index to be configured.
314    /// * deletion_protection: Option<DeletionProtection> - Deletion protection for the index.
315    /// * replicas: Option<i32> - The desired number of replicas, lowest value is 0. This parameter should be `None` if the index is serverless.
316    /// * pod_type: Option<&str> - The new pod_type for the index. This parameter should be `None` if the index is serverless.
317    ///
318    /// ### Return
319    /// * `Result<IndexModel, PineconeError>`
320    ///
321    /// ### Example
322    /// ```no_run
323    /// use pinecone_sdk::models::{DeletionProtection, IndexModel};
324    /// use pinecone_sdk::utils::errors::PineconeError;
325    ///
326    /// # #[tokio::main]
327    /// # async fn main() -> Result<(), PineconeError>{
328    /// let pinecone = pinecone_sdk::pinecone::default_client()?;
329    ///
330    /// // Configure an index in the project.
331    /// let response: Result<IndexModel, PineconeError> = pinecone.configure_index(
332    ///     "index-name",
333    ///     Some(DeletionProtection::Enabled),
334    ///     Some(6),
335    ///     Some("s1.x1")
336    /// ).await;
337    /// # Ok(())
338    /// # }
339    /// ```
340    pub async fn configure_index(
341        &self,
342        name: &str,
343        deletion_protection: Option<DeletionProtection>,
344        replicas: Option<i32>,
345        pod_type: Option<&str>,
346    ) -> Result<IndexModel, PineconeError> {
347        if replicas == None && pod_type == None && deletion_protection == None {
348            return Err(PineconeError::InvalidConfigurationError {
349                message: "At least one of deletion_protection, number of replicas, or pod type must be provided".to_string(),
350            });
351        }
352
353        let spec = match (replicas, pod_type) {
354            (Some(replicas), Some(pod_type)) => Some(Box::new(ConfigureIndexRequestSpec {
355                pod: Box::new(ConfigureIndexRequestSpecPod {
356                    replicas: Some(replicas),
357                    pod_type: Some(pod_type.to_string()),
358                }),
359            })),
360            (Some(replicas), None) => Some(Box::new(ConfigureIndexRequestSpec {
361                pod: Box::new(ConfigureIndexRequestSpecPod {
362                    replicas: Some(replicas),
363                    pod_type: None,
364                }),
365            })),
366            (None, Some(pod_type)) => Some(Box::new(ConfigureIndexRequestSpec {
367                pod: Box::new(ConfigureIndexRequestSpecPod {
368                    replicas: None,
369                    pod_type: Some(pod_type.to_string()),
370                }),
371            })),
372            (None, None) => None,
373        };
374
375        let configure_index_request = ConfigureIndexRequest {
376            spec,
377            deletion_protection,
378        };
379
380        // make openAPI call
381        let res = manage_indexes_api::configure_index(
382            &self.openapi_config,
383            name,
384            configure_index_request,
385        )
386        .await
387        .map_err(|e| PineconeError::from(e))?;
388
389        Ok(res.into())
390    }
391
392    /// Deletes an index.
393    ///
394    /// ### Arguments
395    /// * name: &str - The name of the index to be deleted.
396    ///
397    /// ### Return
398    /// * `Result<(), PineconeError>`
399    ///
400    /// ### Example
401    /// ```no_run
402    /// use pinecone_sdk::utils::errors::PineconeError;
403    ///
404    /// # #[tokio::main]
405    /// # async fn main() -> Result<(), PineconeError>{
406    /// let pinecone = pinecone_sdk::pinecone::default_client()?;
407    ///
408    /// // Delete an index in the project.
409    /// let response: Result<(), PineconeError> = pinecone.delete_index("index-name").await;
410    /// # Ok(())
411    /// # }
412    /// ```
413    pub async fn delete_index(&self, name: &str) -> Result<(), PineconeError> {
414        // make openAPI call
415        let res = manage_indexes_api::delete_index(&self.openapi_config, name)
416            .await
417            .map_err(|e| PineconeError::from(e))?;
418
419        Ok(res)
420    }
421
422    /// Creates a collection from an index.
423    ///
424    /// ### Arguments
425    /// * `name: &str` - Name of the collection to create.
426    /// * `source: &str` - Name of the index to be used as the source for the collection.
427    ///
428    /// ### Return
429    /// * `Result<CollectionModel, PineconeError>`
430    ///
431    /// ### Example
432    /// ```no_run
433    /// use pinecone_sdk::models::CollectionModel;
434    /// use pinecone_sdk::utils::errors::PineconeError;
435    ///
436    /// # #[tokio::main]
437    /// # async fn main() -> Result<(), PineconeError>{
438    /// let pinecone = pinecone_sdk::pinecone::default_client()?;
439    ///
440    /// // Describe an index in the project.
441    /// let response: Result<CollectionModel, PineconeError> = pinecone.create_collection("collection-name", "index-name").await;
442    /// # Ok(())
443    /// # }
444    /// ```
445    pub async fn create_collection(
446        &self,
447        name: &str,
448        source: &str,
449    ) -> Result<CollectionModel, PineconeError> {
450        let create_collection_request = CreateCollectionRequest {
451            name: name.to_string(),
452            source: source.to_string(),
453        };
454
455        // make openAPI call
456        let res =
457            manage_indexes_api::create_collection(&self.openapi_config, create_collection_request)
458                .await
459                .map_err(|e| PineconeError::from(e))?;
460
461        Ok(res)
462    }
463
464    /// Describe a collection.
465    ///
466    /// ### Arguments
467    /// * name: &str - The name of the collection to describe.
468    ///
469    /// ### Return
470    /// * `Result<(), PineconeError>`
471    ///
472    /// ### Example
473    /// ```no_run
474    /// use pinecone_sdk::models::CollectionModel;
475    /// use pinecone_sdk::utils::errors::PineconeError;
476    ///
477    /// # #[tokio::main]
478    /// # async fn main() -> Result<(), PineconeError>{
479    /// let pinecone = pinecone_sdk::pinecone::default_client()?;
480    ///
481    /// // Describe a collection in the project.
482    /// let collection: CollectionModel = pinecone.describe_collection("collection-name").await?;
483    /// # Ok(())
484    /// # }
485    /// ```
486    pub async fn describe_collection(&self, name: &str) -> Result<CollectionModel, PineconeError> {
487        let res = manage_indexes_api::describe_collection(&self.openapi_config, name)
488            .await
489            .map_err(|e| PineconeError::from(e))?;
490
491        Ok(res)
492    }
493
494    /// Lists all collections.
495    ///
496    /// This operation returns a list of all collections in a project.
497    ///
498    /// ### Return
499    /// * `Result<CollectionList, PineconeError>`
500    ///
501    /// ### Example
502    /// ```no_run
503    /// use pinecone_sdk::models::CollectionList;
504    /// use pinecone_sdk::utils::errors::PineconeError;
505    ///
506    /// # #[tokio::main]
507    /// # async fn main() -> Result<(), PineconeError>{
508    /// let pinecone = pinecone_sdk::pinecone::default_client()?;
509    ///
510    /// // List all collections in the project.
511    /// let response: Result<CollectionList, PineconeError> = pinecone.list_collections().await;
512    /// # Ok(())
513    /// # }
514    /// ```
515    pub async fn list_collections(&self) -> Result<CollectionList, PineconeError> {
516        // make openAPI call
517        let res = manage_indexes_api::list_collections(&self.openapi_config)
518            .await
519            .map_err(|e| PineconeError::from(e))?;
520
521        Ok(res)
522    }
523
524    /// Deletes a collection.
525    ///
526    /// ### Arguments
527    /// * name: &str - The name of the collection to be deleted.
528    ///
529    /// ### Return
530    /// * `Result<(), PineconeError>`
531    ///
532    /// ### Example
533    /// ```no_run
534    /// use pinecone_sdk::utils::errors::PineconeError;
535    ///
536    /// # #[tokio::main]
537    /// # async fn main() -> Result<(), PineconeError>{
538    /// let pinecone = pinecone_sdk::pinecone::default_client()?;
539    ///
540    /// // Delete a collection in the project.
541    /// let response: Result<(), PineconeError> = pinecone.delete_collection("collection-name").await;
542    /// # Ok(())
543    /// # }
544    /// ```
545    pub async fn delete_collection(&self, name: &str) -> Result<(), PineconeError> {
546        // make openAPI call
547        let res = manage_indexes_api::delete_collection(&self.openapi_config, name)
548            .await
549            .map_err(|e| PineconeError::from(e))?;
550
551        Ok(res)
552    }
553}
554
555#[cfg(test)]
556mod tests {
557    use super::*;
558    use crate::openapi::{
559        self,
560        models::{self, collection_model::Status},
561    };
562    use crate::pinecone::PineconeClientConfig;
563    use httpmock::prelude::*;
564    use tokio;
565
566    #[tokio::test]
567    async fn test_create_serverless_index() -> Result<(), PineconeError> {
568        let server = MockServer::start();
569
570        let mock = server.mock(|when, then| {
571            when.method(POST).path("/indexes");
572            then.status(201)
573                .header("content-type", "application/json")
574                .body(
575                    r#"
576                {
577                    "name": "index-name",
578                    "dimension": 10,
579                    "metric": "euclidean",
580                    "host": "host1",
581                    "spec": {
582                        "serverless": {
583                            "cloud": "aws",
584                            "region": "us-east-1"
585                        }
586                    },
587                    "status": {
588                        "ready": true,
589                        "state": "Initializing"
590                    }
591                }"#,
592                );
593        });
594
595        let config = PineconeClientConfig {
596            api_key: Some("api_key".to_string()),
597            control_plane_host: Some(server.base_url()),
598            ..Default::default()
599        };
600        let pinecone = config.client().expect("Failed to create Pinecone instance");
601
602        let create_index_response = pinecone
603            .create_serverless_index(
604                "index-name",
605                10,
606                Metric::Cosine,
607                Cloud::Aws,
608                "us-east-1",
609                DeletionProtection::Enabled,
610                WaitPolicy::NoWait,
611            )
612            .await
613            .expect("Failed to create serverless index");
614
615        mock.assert();
616
617        assert_eq!(create_index_response.name, "index-name");
618        assert_eq!(create_index_response.dimension, 10);
619        assert_eq!(create_index_response.metric, Metric::Euclidean);
620
621        let spec = create_index_response.spec.serverless.unwrap();
622        assert_eq!(spec.cloud, openapi::models::serverless_spec::Cloud::Aws);
623        assert_eq!(spec.region, "us-east-1");
624
625        Ok(())
626    }
627
628    #[tokio::test]
629    async fn test_create_serverless_index_defaults() -> Result<(), PineconeError> {
630        let server = MockServer::start();
631
632        let mock = server.mock(|when, then| {
633            when.method(POST).path("/indexes");
634            then.status(201)
635                .header("content-type", "application/json")
636                .body(
637                    r#"{
638                    "name": "index-name",
639                    "dimension": 10,
640                    "metric": "cosine",
641                    "host": "host1",
642                    "spec": {
643                        "serverless": {
644                            "cloud": "gcp",
645                            "region": "us-east-1"
646                        }
647                    },
648                    "status": {
649                        "ready": true,
650                        "state": "Initializing"
651                    }
652                }"#,
653                );
654        });
655
656        let config = PineconeClientConfig {
657            api_key: Some("api_key".to_string()),
658            control_plane_host: Some(server.base_url()),
659            ..Default::default()
660        };
661        let pinecone = config.client().expect("Failed to create Pinecone instance");
662
663        let create_index_response = pinecone
664            .create_serverless_index(
665                "index-name",
666                10,
667                Default::default(),
668                Default::default(),
669                "us-east-1",
670                DeletionProtection::Enabled,
671                WaitPolicy::NoWait,
672            )
673            .await
674            .expect("Failed to create serverless index");
675
676        assert_eq!(create_index_response.name, "index-name");
677        assert_eq!(create_index_response.dimension, 10);
678        assert_eq!(create_index_response.metric, Metric::Cosine);
679
680        let spec = create_index_response.spec.serverless.unwrap();
681        assert_eq!(spec.cloud, openapi::models::serverless_spec::Cloud::Gcp);
682        assert_eq!(spec.region, "us-east-1");
683
684        mock.assert();
685
686        Ok(())
687    }
688
689    #[tokio::test]
690    async fn test_create_serverless_index_invalid_region() -> Result<(), PineconeError> {
691        let server = MockServer::start();
692
693        let mock = server.mock(|when, then| {
694            when.method(POST).path("/indexes");
695            then.status(404)
696                .header("content-type", "application/json")
697                .body(
698                    r#"{
699                    "error": {
700                        "code": "NOT_FOUND",
701                        "message": "Resource cloud: aws region: abc not found."
702                    },
703                    "status": 404
704                }"#,
705                );
706        });
707
708        let config = PineconeClientConfig {
709            api_key: Some("api_key".to_string()),
710            control_plane_host: Some(server.base_url()),
711            ..Default::default()
712        };
713        let pinecone = config.client().expect("Failed to create Pinecone instance");
714
715        let create_index_response = pinecone
716            .create_serverless_index(
717                "index-name",
718                10,
719                Default::default(),
720                Default::default(),
721                "abc",
722                DeletionProtection::Enabled,
723                WaitPolicy::NoWait,
724            )
725            .await
726            .expect_err("Expected error when creating serverless index");
727
728        assert!(matches!(
729            create_index_response,
730            PineconeError::InvalidRegionError { .. }
731        ));
732        mock.assert();
733
734        Ok(())
735    }
736
737    #[tokio::test]
738    async fn test_create_serverless_index_index_exists() -> Result<(), PineconeError> {
739        let server = MockServer::start();
740
741        let mock = server.mock(|when, then| {
742            when.method(POST).path("/indexes");
743            then.status(409)
744                .header("content-type", "application/json")
745                .body(
746                    r#"{
747                        "error": {
748                            "code": "ALREADY_EXISTS",
749                            "message": "Resource already exists."
750                        },
751                        "status": 409
752                    }"#,
753                );
754        });
755
756        let config = PineconeClientConfig {
757            api_key: Some("api_key".to_string()),
758            control_plane_host: Some(server.base_url()),
759            ..Default::default()
760        };
761        let pinecone = config.client().expect("Failed to create Pinecone instance");
762
763        let create_index_response = pinecone
764            .create_serverless_index(
765                "index-name",
766                10,
767                Default::default(),
768                Default::default(),
769                "us-west-1",
770                DeletionProtection::Enabled,
771                WaitPolicy::NoWait,
772            )
773            .await
774            .expect_err("Expected error when creating serverless index");
775
776        assert!(matches!(
777            create_index_response,
778            PineconeError::ResourceAlreadyExistsError { .. }
779        ));
780        mock.assert();
781
782        Ok(())
783    }
784
785    #[tokio::test]
786    async fn test_create_serverless_index_unprocessable_entity() -> Result<(), PineconeError> {
787        let server = MockServer::start();
788
789        let mock = server.mock(|when, then| {
790            when.method(POST).path("/indexes");
791            then.status(422)
792                .header("content-type", "application/json")
793                .body(
794                r#"{
795                    "error": {
796                            "code": "INVALID_ARGUMENT",
797                            "message": "Failed to deserialize the JSON body into the target type: missing field `metric` at line 1 column 16"
798                        },
799                    "status": 422
800                }"#,
801            );
802        });
803
804        let config = PineconeClientConfig {
805            api_key: Some("api_key".to_string()),
806            control_plane_host: Some(server.base_url()),
807            ..Default::default()
808        };
809        let pinecone = config.client().expect("Failed to create Pinecone instance");
810
811        let create_index_response = pinecone
812            .create_serverless_index(
813                "index-name",
814                10,
815                Default::default(),
816                Default::default(),
817                "us-west-1",
818                DeletionProtection::Enabled,
819                WaitPolicy::NoWait,
820            )
821            .await
822            .expect_err("Expected error when creating serverless index");
823
824        assert!(matches!(
825            create_index_response,
826            PineconeError::UnprocessableEntityError { .. }
827        ));
828        mock.assert();
829
830        Ok(())
831    }
832
833    #[tokio::test]
834    async fn test_create_serverless_index_internal_error() -> Result<(), PineconeError> {
835        let server = MockServer::start();
836
837        let mock = server.mock(|when, then| {
838            when.method(POST).path("/indexes");
839            then.status(500);
840        });
841
842        let config = PineconeClientConfig {
843            api_key: Some("api_key".to_string()),
844            control_plane_host: Some(server.base_url()),
845            ..Default::default()
846        };
847        let pinecone = config.client().expect("Failed to create Pinecone instance");
848
849        let create_index_response = pinecone
850            .create_serverless_index(
851                "index-name",
852                10,
853                Metric::Cosine,
854                Cloud::Aws,
855                "us-east-1",
856                DeletionProtection::Enabled,
857                WaitPolicy::NoWait,
858            )
859            .await
860            .expect_err("Expected create_index to return an error");
861
862        assert!(matches!(
863            create_index_response,
864            PineconeError::InternalServerError { .. }
865        ));
866        mock.assert();
867
868        Ok(())
869    }
870
871    #[tokio::test]
872    async fn test_describe_serverless_index() -> Result<(), PineconeError> {
873        let server = MockServer::start();
874
875        let mock = server.mock(|when, then| {
876            when.method(GET).path("/indexes/serverless-index");
877            then.status(200)
878                .header("content-type", "application/json")
879                .body(
880                    r#"{
881                        "dimension": 1536,
882                        "host": "mock-host",
883                        "metric": "cosine",
884                        "name": "serverless-index",
885                        "spec": {
886                            "serverless": {
887                            "cloud": "aws",
888                            "region": "us-east-1"
889                            }
890                        },
891                        "deletion_protection": "disabled",
892                        "status": {
893                            "ready": true,
894                            "state": "Ready"
895                        }
896                    }"#,
897                );
898        });
899
900        // Construct Pinecone instance with the mock server URL
901        let config = PineconeClientConfig {
902            api_key: Some("api_key".to_string()),
903            control_plane_host: Some(server.base_url()),
904            ..Default::default()
905        };
906        let pinecone = config.client().expect("Failed to create Pinecone instance");
907
908        // Call describe_index and verify the result
909        let index = pinecone
910            .describe_index("serverless-index")
911            .await
912            .expect("Failed to describe index");
913
914        let expected = IndexModel {
915            name: "serverless-index".to_string(),
916            metric: Metric::Cosine,
917            dimension: 1536,
918            status: openapi::models::IndexModelStatus {
919                ready: true,
920                state: openapi::models::index_model_status::State::Ready,
921            },
922            host: "mock-host".to_string(),
923            deletion_protection: Some(DeletionProtection::Disabled),
924            spec: models::IndexModelSpec {
925                serverless: Some(Box::new(models::ServerlessSpec {
926                    cloud: openapi::models::serverless_spec::Cloud::Aws,
927                    region: "us-east-1".to_string(),
928                })),
929                pod: None,
930            },
931        };
932
933        assert_eq!(index, expected);
934        mock.assert();
935
936        Ok(())
937    }
938
939    #[tokio::test]
940    async fn test_describe_index_invalid_name() -> Result<(), PineconeError> {
941        let server = MockServer::start();
942
943        let mock = server.mock(|when, then| {
944            when.method(GET).path("/indexes/invalid-index");
945            then.status(404)
946                .header("content-type", "application/json")
947                .body(
948                    r#"{
949                    "error": "Index invalid-index not found"
950                }"#,
951                );
952        });
953
954        let config = PineconeClientConfig {
955            api_key: Some("api_key".to_string()),
956            control_plane_host: Some(server.base_url()),
957            ..Default::default()
958        };
959        let pinecone = config.client().expect("Failed to create Pinecone instance");
960
961        let describe_index_response = pinecone
962            .describe_index("invalid-index")
963            .await
964            .expect_err("Expected describe_index to return an error");
965
966        assert!(matches!(
967            describe_index_response,
968            PineconeError::IndexNotFoundError { .. }
969        ));
970        mock.assert();
971
972        Ok(())
973    }
974
975    #[tokio::test]
976    async fn test_describe_index_server_error() -> Result<(), PineconeError> {
977        let server = MockServer::start();
978
979        let mock = server.mock(|when, then| {
980            when.method(GET).path("/indexes/index-name");
981            then.status(500);
982        });
983
984        let config = PineconeClientConfig {
985            api_key: Some("api_key".to_string()),
986            control_plane_host: Some(server.base_url()),
987            ..Default::default()
988        };
989        let pinecone = config.client().expect("Failed to create Pinecone instance");
990
991        let describe_index_response = pinecone
992            .describe_index("index-name")
993            .await
994            .expect_err("Expected describe_index to return an error");
995
996        assert!(matches!(
997            describe_index_response,
998            PineconeError::InternalServerError { .. }
999        ));
1000        mock.assert();
1001
1002        Ok(())
1003    }
1004
1005    #[tokio::test]
1006    async fn test_list_indexes() -> Result<(), PineconeError> {
1007        let server = MockServer::start();
1008
1009        let mock = server.mock(|when, then| {
1010            when.method(GET).path("/indexes");
1011            then.status(200)
1012                .header("content-type", "application/json")
1013                .body(
1014                    r#"
1015                {
1016                    "indexes": [
1017                        {
1018                            "name": "index1",
1019                            "dimension": 1536,
1020                            "metric": "cosine",
1021                            "host": "host1",
1022                            "spec": {},
1023                            "status": {
1024                                "ready": false,
1025                                "state": "Initializing"
1026                            }
1027                        },
1028                        {
1029                            "name": "index2",
1030                            "dimension": 1536,
1031                            "metric": "cosine",
1032                            "host": "host2",
1033                            "spec": {},
1034                            "status": {
1035                                "ready": false,
1036                                "state": "Initializing"
1037                            }
1038                        }
1039                    ]
1040                }"#,
1041                );
1042        });
1043
1044        // Construct Pinecone instance with the mock server URL
1045        let config = PineconeClientConfig {
1046            api_key: Some("api_key".to_string()),
1047            control_plane_host: Some(server.base_url()),
1048            ..Default::default()
1049        };
1050        let pinecone = config.client().expect("Failed to create Pinecone instance");
1051
1052        // Call list_indexes and verify the result
1053        let index_list = pinecone
1054            .list_indexes()
1055            .await
1056            .expect("Failed to list indexes");
1057
1058        let expected = IndexList {
1059            // name: String, dimension: i32, metric: Metric, host: String, spec: models::IndexModelSpec, status: models::IndexModelStatus)
1060            indexes: Some(vec![
1061                IndexModel {
1062                    name: "index1".to_string(),
1063                    dimension: 1536,
1064                    metric: Metric::Cosine,
1065                    host: "host1".to_string(),
1066                    deletion_protection: None,
1067                    spec: models::IndexModelSpec::default(),
1068                    status: models::IndexModelStatus::default(),
1069                },
1070                IndexModel {
1071                    name: "index2".to_string(),
1072                    dimension: 1536,
1073                    metric: Metric::Cosine,
1074                    host: "host2".to_string(),
1075                    deletion_protection: None,
1076                    spec: models::IndexModelSpec::default(),
1077                    status: models::IndexModelStatus::default(),
1078                },
1079            ]),
1080        };
1081        assert_eq!(index_list, expected);
1082        mock.assert();
1083
1084        Ok(())
1085    }
1086
1087    #[tokio::test]
1088    async fn test_list_indexes_server_error() -> Result<(), PineconeError> {
1089        let server = MockServer::start();
1090
1091        let mock = server.mock(|when, then| {
1092            when.method(GET).path("/indexes");
1093            then.status(500);
1094        });
1095
1096        let config = PineconeClientConfig {
1097            api_key: Some("api_key".to_string()),
1098            control_plane_host: Some(server.base_url()),
1099            ..Default::default()
1100        };
1101        let pinecone = config.client().expect("Failed to create Pinecone instance");
1102
1103        let list_indexes_response = pinecone
1104            .list_indexes()
1105            .await
1106            .expect_err("Expected list_indexes to return an error");
1107
1108        assert!(matches!(
1109            list_indexes_response,
1110            PineconeError::InternalServerError { .. }
1111        ));
1112        mock.assert();
1113
1114        Ok(())
1115    }
1116
1117    #[tokio::test]
1118    async fn test_create_pod_index() -> Result<(), PineconeError> {
1119        let server = MockServer::start();
1120
1121        let mock = server.mock(|when, then| {
1122            when.method(POST).path("/indexes");
1123            then.status(201)
1124                .header("content-type", "application/json")
1125                .body(
1126                    r#"
1127                {
1128                    "name": "index-name",
1129                    "dimension": 1536,
1130                    "metric": "euclidean",
1131                    "host": "mock-host",
1132                    "spec": {
1133                        "pod": {
1134                            "environment": "us-east-1-aws",
1135                            "replicas": 1,
1136                            "shards": 1,
1137                            "pod_type": "p1.x1",
1138                            "pods": 1,
1139                            "metadata_config": {
1140                                "indexed": [
1141                                    "genre",
1142                                    "title",
1143                                    "imdb_rating"
1144                                ]
1145                            }
1146                        }
1147                    },
1148                    "status": {
1149                        "ready": true,
1150                        "state": "ScalingUpPodSize"
1151                    }
1152                }
1153            "#,
1154                );
1155        });
1156
1157        let config = PineconeClientConfig {
1158            api_key: Some("api_key".to_string()),
1159            control_plane_host: Some(server.base_url()),
1160            ..Default::default()
1161        };
1162        let pinecone = config.client().expect("Failed to create Pinecone instance");
1163
1164        let create_index_response = pinecone
1165            .create_pod_index(
1166                "index-name",
1167                1536,
1168                Metric::Euclidean,
1169                "us-east-1-aws",
1170                "p1.x1",
1171                1,
1172                1,
1173                1,
1174                DeletionProtection::Enabled,
1175                Some(&vec!["genre", "title", "imdb_rating"]),
1176                Some("example-collection"),
1177                WaitPolicy::NoWait,
1178            )
1179            .await
1180            .expect("Failed to create pod index");
1181
1182        assert_eq!(create_index_response.name, "index-name");
1183        assert_eq!(create_index_response.dimension, 1536);
1184        assert_eq!(create_index_response.metric, Metric::Euclidean);
1185
1186        let pod_spec = create_index_response.spec.pod.as_ref().unwrap();
1187        assert_eq!(pod_spec.environment, "us-east-1-aws");
1188        assert_eq!(pod_spec.pod_type, "p1.x1");
1189        assert_eq!(
1190            pod_spec.metadata_config.as_ref().unwrap().indexed,
1191            Some(vec![
1192                "genre".to_string(),
1193                "title".to_string(),
1194                "imdb_rating".to_string()
1195            ])
1196        );
1197        assert_eq!(pod_spec.pods, 1);
1198        assert_eq!(pod_spec.replicas, 1);
1199        assert_eq!(pod_spec.shards, 1);
1200
1201        mock.assert();
1202
1203        Ok(())
1204    }
1205
1206    #[tokio::test]
1207    async fn test_create_pod_index_with_defaults() -> Result<(), PineconeError> {
1208        let server = MockServer::start();
1209
1210        let mock = server.mock(|when, then| {
1211            when.method(POST).path("/indexes");
1212            then.status(201)
1213                .header("content-type", "application/json")
1214                .body(
1215                    r#"
1216                {
1217                    "name": "index-name",
1218                    "dimension": 1536,
1219                    "metric": "cosine",
1220                    "host": "mock-host",
1221                    "spec": {
1222                        "pod": {
1223                            "environment": "us-east-1-aws",
1224                            "pod_type": "p1.x1",
1225                            "pods": 1,
1226                            "metadata_config": {},
1227                            "replicas": 1,
1228                            "shards": 1
1229                        }
1230                    },
1231                    "status": {
1232                        "ready": true,
1233                        "state": "ScalingUpPodSize"
1234                    }
1235                }
1236            "#,
1237                );
1238        });
1239
1240        let config = PineconeClientConfig {
1241            api_key: Some("api_key".to_string()),
1242            control_plane_host: Some(server.base_url()),
1243            ..Default::default()
1244        };
1245        let pinecone = config.client().expect("Failed to create Pinecone instance");
1246
1247        let create_index_response = pinecone
1248            .create_pod_index(
1249                "index-name",
1250                1536,
1251                Default::default(),
1252                "us-east-1-aws",
1253                "p1.x1",
1254                1,
1255                1,
1256                1,
1257                DeletionProtection::Enabled,
1258                None,
1259                None,
1260                WaitPolicy::NoWait,
1261            )
1262            .await
1263            .expect("Failed to create pod index");
1264
1265        assert_eq!(create_index_response.name, "index-name");
1266        assert_eq!(create_index_response.dimension, 1536);
1267        assert_eq!(create_index_response.metric, Metric::Cosine);
1268
1269        let pod_spec = create_index_response.spec.pod.as_ref().unwrap();
1270        assert_eq!(pod_spec.environment, "us-east-1-aws");
1271        assert_eq!(pod_spec.pod_type, "p1.x1");
1272        assert_eq!(pod_spec.metadata_config.as_ref().unwrap().indexed, None);
1273        assert_eq!(pod_spec.pods, 1);
1274        assert_eq!(pod_spec.replicas, 1);
1275        assert_eq!(pod_spec.shards, 1);
1276
1277        mock.assert();
1278
1279        Ok(())
1280    }
1281
1282    #[tokio::test]
1283    async fn test_create_pod_index_quota_exceeded() -> Result<(), PineconeError> {
1284        let server = MockServer::start();
1285
1286        let mock = server.mock(|when, then| {
1287            when.method(POST).path("/indexes");
1288            then.status(403)
1289                .header("content-type", "application/json")
1290                .body(
1291                    r#"
1292                    {
1293                        "error": {
1294                            "code": "FORBIDDEN",
1295                            "message": "Increase yoru quota or upgrade to create more indexes."
1296                        },
1297                        "status": 403
1298                    }
1299                "#,
1300                );
1301        });
1302
1303        let config = PineconeClientConfig {
1304            api_key: Some("api_key".to_string()),
1305            control_plane_host: Some(server.base_url()),
1306            ..Default::default()
1307        };
1308        let pinecone = config.client().expect("Failed to create Pinecone instance");
1309
1310        let create_index_response = pinecone
1311            .create_pod_index(
1312                "index-name",
1313                1536,
1314                Metric::Euclidean,
1315                "test-environment",
1316                "p1.x1",
1317                1,
1318                1,
1319                1,
1320                DeletionProtection::Enabled,
1321                None,
1322                Some("example-collection"),
1323                WaitPolicy::NoWait,
1324            )
1325            .await
1326            .expect_err("Expected create_pod_index to return an error");
1327
1328        assert!(matches!(
1329            create_index_response,
1330            PineconeError::PodQuotaExceededError { .. }
1331        ));
1332
1333        mock.assert();
1334
1335        Ok(())
1336    }
1337
1338    #[tokio::test]
1339    async fn test_create_pod_index_invalid_environment() -> Result<(), PineconeError> {
1340        let server = MockServer::start();
1341
1342        let mock = server.mock(|when, then| {
1343            when.method(POST).path("/indexes");
1344            then.status(400)
1345                .header("content-type", "application/json")
1346                .body(
1347                    r#"
1348                    {
1349                        "error": "Invalid environment"
1350                    }
1351                "#,
1352                );
1353        });
1354
1355        let config = PineconeClientConfig {
1356            api_key: Some("api_key".to_string()),
1357            control_plane_host: Some(server.base_url()),
1358            ..Default::default()
1359        };
1360        let pinecone = config.client().expect("Failed to create Pinecone instance");
1361
1362        let create_index_response = pinecone
1363            .create_pod_index(
1364                "index-name",
1365                1536,
1366                Metric::Euclidean,
1367                "invalid-environment",
1368                "p1.x1",
1369                1,
1370                1,
1371                1,
1372                DeletionProtection::Enabled,
1373                Some(&vec!["genre", "title", "imdb_rating"]),
1374                Some("example-collection"),
1375                WaitPolicy::NoWait,
1376            )
1377            .await
1378            .expect_err("Expected create_pod_index to return an error");
1379
1380        assert!(matches!(
1381            create_index_response,
1382            PineconeError::BadRequestError { .. }
1383        ));
1384
1385        mock.assert();
1386
1387        Ok(())
1388    }
1389
1390    #[tokio::test]
1391    async fn test_create_pod_index_invalid_pod_type() -> Result<(), PineconeError> {
1392        let server = MockServer::start();
1393
1394        let mock = server.mock(|when, then| {
1395            when.method(POST).path("/indexes");
1396            then.status(400)
1397                .header("content-type", "application/json")
1398                .body(
1399                    r#"
1400                    {
1401                        "error": "Invalid pod type"
1402                    }
1403                "#,
1404                );
1405        });
1406
1407        let config = PineconeClientConfig {
1408            api_key: Some("api_key".to_string()),
1409            control_plane_host: Some(server.base_url()),
1410            ..Default::default()
1411        };
1412        let pinecone = config.client().expect("Failed to create Pinecone instance");
1413
1414        let create_index_response = pinecone
1415            .create_pod_index(
1416                "index-name",
1417                1536,
1418                Metric::Euclidean,
1419                "us-east-1-aws",
1420                "invalid-pod-type",
1421                1,
1422                1,
1423                1,
1424                DeletionProtection::Enabled,
1425                Some(&vec!["genre", "title", "imdb_rating"]),
1426                Some("example-collection"),
1427                WaitPolicy::NoWait,
1428            )
1429            .await
1430            .expect_err("Expected create_pod_index to return an error");
1431
1432        assert!(matches!(
1433            create_index_response,
1434            PineconeError::BadRequestError { .. }
1435        ));
1436        mock.assert();
1437
1438        Ok(())
1439    }
1440
1441    #[tokio::test]
1442    async fn test_handle_polling_index_ok() -> Result<(), PineconeError> {
1443        let server = MockServer::start();
1444
1445        let mock = server.mock(|when, then| {
1446            when.method(GET).path("/indexes/index-name");
1447            then.status(200)
1448                .header("content-type", "application/json")
1449                .body(
1450                    r#"
1451                {
1452                    "dimension": 1536,
1453                    "host": "mock-host",
1454                    "metric": "cosine",
1455                    "name": "index-name",
1456                    "spec": {
1457                        "serverless": {
1458                        "cloud": "aws",
1459                        "region": "us-east-1"
1460                        }
1461                    },
1462                    "status": {
1463                        "ready": true,
1464                        "state": "Ready"
1465                    }
1466                }"#,
1467                );
1468        });
1469
1470        let config = PineconeClientConfig {
1471            api_key: Some("api_key".to_string()),
1472            control_plane_host: Some(server.base_url()),
1473            ..Default::default()
1474        };
1475        let pinecone = config.client().expect("Failed to create Pinecone instance");
1476
1477        let res = pinecone
1478            .handle_poll_index("index-name", WaitPolicy::WaitFor(Duration::from_secs(1)))
1479            .await;
1480
1481        assert!(res.is_ok());
1482        mock.assert();
1483
1484        Ok(())
1485    }
1486
1487    #[tokio::test]
1488    async fn test_handle_polling_index_err() -> Result<(), PineconeError> {
1489        let server = MockServer::start();
1490
1491        let mock = server.mock(|when, then| {
1492            when.method(GET).path("/indexes/index-name");
1493            then.status(200)
1494                .header("content-type", "application/json")
1495                .body(
1496                    r#"
1497                    {
1498                        "dimension": 1536,
1499                        "host": "mock-host",
1500                        "metric": "cosine",
1501                        "name": "index-name",
1502                        "spec": {
1503                            "serverless": {
1504                            "cloud": "aws",
1505                            "region": "us-east-1"
1506                            }
1507                        },
1508                        "status": {
1509                            "ready": false,
1510                            "state": "Initializing"
1511                        }
1512                    }"#,
1513                );
1514        });
1515
1516        let config = PineconeClientConfig {
1517            api_key: Some("api_key".to_string()),
1518            control_plane_host: Some(server.base_url()),
1519            ..Default::default()
1520        };
1521        let pinecone = config.client().expect("Failed to create Pinecone instance");
1522
1523        let start_time = std::time::Instant::now();
1524        let err = pinecone
1525            .handle_poll_index("index-name", WaitPolicy::WaitFor(Duration::from_secs(7)))
1526            .await
1527            .expect_err("Expected to fail polling index");
1528
1529        assert!(start_time.elapsed().as_secs() >= 7 && start_time.elapsed().as_secs() < 8);
1530        assert!(matches!(err, PineconeError::TimeoutError { .. }));
1531
1532        mock.assert_hits(3);
1533
1534        Ok(())
1535    }
1536
1537    #[tokio::test]
1538    async fn test_configure_index() -> Result<(), PineconeError> {
1539        let server = MockServer::start();
1540
1541        let mock = server.mock(|when, then| {
1542            when.path("/indexes/index-name");
1543            then.status(202)
1544                .header("content-type", "application/json")
1545                .body(
1546                    r#"
1547                {
1548                    "name": "index-name",
1549                    "dimension": 1536,
1550                    "metric": "cosine",
1551                    "host": "mock-host",
1552                    "spec": {
1553                        "pod": {
1554                            "environment": "us-east-1-aws",
1555                            "replicas": 6,
1556                            "shards": 1,
1557                            "pod_type": "p1.x1",
1558                            "pods": 1,
1559                            "metadata_config": {
1560                                "indexed": [
1561                                    "genre",
1562                                    "title",
1563                                    "imdb_rating"
1564                                ]
1565                            }
1566                        }
1567                    },
1568                    "status": {
1569                        "ready": true,
1570                        "state": "ScalingUpPodSize"
1571                    }
1572                }"#,
1573                );
1574        });
1575
1576        let config = PineconeClientConfig {
1577            api_key: Some("api_key".to_string()),
1578            control_plane_host: Some(server.base_url()),
1579            ..Default::default()
1580        };
1581        let pinecone = config.client().expect("Failed to create Pinecone instance");
1582
1583        let configure_index_response = pinecone
1584            .configure_index(
1585                "index-name",
1586                Some(DeletionProtection::Disabled),
1587                Some(6),
1588                Some("p1.x1"),
1589            )
1590            .await
1591            .expect("Failed to configure index");
1592
1593        assert_eq!(configure_index_response.name, "index-name");
1594
1595        let spec = configure_index_response.spec.pod.unwrap();
1596        assert_eq!(spec.replicas, 6);
1597        assert_eq!(spec.pod_type.as_str(), "p1.x1");
1598
1599        mock.assert();
1600
1601        Ok(())
1602    }
1603
1604    #[tokio::test]
1605    async fn test_configure_deletion_protection() -> Result<(), PineconeError> {
1606        let server = MockServer::start();
1607
1608        let mock = server.mock(|when, then| {
1609            when.path("/indexes/index-name");
1610            then.status(202)
1611                .header("content-type", "application/json")
1612                .body(
1613                    r#"{
1614                        "name": "index-name",
1615                        "dimension": 1536,
1616                        "metric": "cosine",
1617                        "host": "mock-host",
1618                        "deletion_protection": "disabled",
1619                        "spec": {
1620                            "pod": {
1621                                "environment": "us-east-1-aws",
1622                                "metadata_config": {
1623                                    "indexed": [
1624                                        "genre",
1625                                        "title",
1626                                        "imdb_rating"
1627                                    ]
1628                                },
1629                                "pod_type": "p1.x1",
1630                                "pods": 1,
1631                                "replicas": 1,
1632                                "shards": 1
1633                            }
1634                        },
1635                        "status": {
1636                            "ready": true,
1637                            "state": "ScalingUpPodSize"
1638                        }
1639                    }"#,
1640                );
1641        });
1642
1643        let config = PineconeClientConfig {
1644            api_key: Some("api_key".to_string()),
1645            control_plane_host: Some(server.base_url()),
1646            ..Default::default()
1647        };
1648        let pinecone = config.client().expect("Failed to create Pinecone instance");
1649
1650        let configure_index_response = pinecone
1651            .configure_index("index-name", Some(DeletionProtection::Disabled), None, None)
1652            .await
1653            .expect("Failed to configure index");
1654
1655        assert_eq!(
1656            configure_index_response.deletion_protection,
1657            Some(DeletionProtection::Disabled)
1658        );
1659
1660        mock.assert();
1661
1662        Ok(())
1663    }
1664
1665    #[tokio::test]
1666    async fn test_configure_index_no_config() -> Result<(), PineconeError> {
1667        let config = PineconeClientConfig {
1668            api_key: Some("api_key".to_string()),
1669            ..Default::default()
1670        };
1671        let pinecone = config.client().expect("Failed to create Pinecone instance");
1672
1673        let configure_index_response = pinecone
1674            .configure_index("index-name", None, None, None)
1675            .await;
1676
1677        assert!(matches!(
1678            configure_index_response,
1679            Err(PineconeError::InvalidConfigurationError { .. })
1680        ));
1681
1682        Ok(())
1683    }
1684
1685    #[tokio::test]
1686    async fn test_configure_index_quota_exceeded() -> Result<(), PineconeError> {
1687        let server = MockServer::start();
1688
1689        let mock = server.mock(|when, then| {
1690            when.path("/indexes/index-name");
1691            then.status(403)
1692                .header("content-type", "application/json")
1693                .body(
1694                    r#"
1695                    {
1696                        "error": {
1697                            "code": "FORBIDDEN",
1698                            "message": "Increase your quota or upgrade to create more indexes."
1699                        },
1700                        "status": 403
1701                    }
1702                "#,
1703                );
1704        });
1705
1706        let config = PineconeClientConfig {
1707            api_key: Some("api_key".to_string()),
1708            control_plane_host: Some(server.base_url()),
1709            ..Default::default()
1710        };
1711        let pinecone = config.client().expect("Failed to create Pinecone instance");
1712
1713        let configure_index_response = pinecone
1714            .configure_index(
1715                "index-name",
1716                Some(DeletionProtection::Enabled),
1717                Some(6),
1718                Some("p1.x1"),
1719            )
1720            .await
1721            .expect_err("Expected to fail to configure index");
1722
1723        assert!(matches!(
1724            configure_index_response,
1725            PineconeError::PodQuotaExceededError { .. }
1726        ));
1727
1728        mock.assert();
1729
1730        Ok(())
1731    }
1732
1733    #[tokio::test]
1734    async fn test_configure_index_not_found() -> Result<(), PineconeError> {
1735        let server = MockServer::start();
1736
1737        let mock = server.mock(|when, then| {
1738            when.path("/indexes/index-name");
1739            then.status(404)
1740                .header("content-type", "application/json")
1741                .body(
1742                    r#"{
1743                    "error": {
1744                        "code": "NOT_FOUND",
1745                        "message": "Index index-name not found."
1746                    },
1747                    "status": 404
1748                }"#,
1749                );
1750        });
1751
1752        let config = PineconeClientConfig {
1753            api_key: Some("api_key".to_string()),
1754            control_plane_host: Some(server.base_url()),
1755            ..Default::default()
1756        };
1757        let pinecone = config.client().expect("Failed to create Pinecone instance");
1758
1759        let configure_index_response = pinecone
1760            .configure_index(
1761                "index-name",
1762                Some(DeletionProtection::Disabled),
1763                Some(6),
1764                Some("p1.x1"),
1765            )
1766            .await
1767            .expect_err("Expected to fail to configure index");
1768
1769        assert!(matches!(
1770            configure_index_response,
1771            PineconeError::IndexNotFoundError { .. }
1772        ));
1773
1774        mock.assert();
1775
1776        Ok(())
1777    }
1778
1779    #[tokio::test]
1780    async fn test_configure_index_unprocessable_entity() -> Result<(), PineconeError> {
1781        let server = MockServer::start();
1782
1783        let mock = server.mock(|when, then| {
1784            when.path("/indexes/index-name");
1785            then.status(422)
1786                .header("content-type", "application/json")
1787                .body(
1788                    r#"{
1789                    "error": {
1790                            "code": "INVALID_ARGUMENT",
1791                            "message": "Failed to deserialize the JSON body into the target type
1792                        },
1793                    "status": 422
1794                }"#,
1795                );
1796        });
1797
1798        let config = PineconeClientConfig {
1799            api_key: Some("api_key".to_string()),
1800            control_plane_host: Some(server.base_url()),
1801            ..Default::default()
1802        };
1803        let pinecone = config.client().expect("Failed to create Pinecone instance");
1804
1805        let configure_index_response = pinecone
1806            .configure_index(
1807                "index-name",
1808                Some(DeletionProtection::Enabled),
1809                Some(6),
1810                Some("p1.x1"),
1811            )
1812            .await
1813            .expect_err("Expected to fail to configure index");
1814
1815        assert!(matches!(
1816            configure_index_response,
1817            PineconeError::UnprocessableEntityError { .. }
1818        ));
1819
1820        mock.assert();
1821
1822        Ok(())
1823    }
1824
1825    #[tokio::test]
1826    async fn test_configure_index_internal_error() -> Result<(), PineconeError> {
1827        let server = MockServer::start();
1828
1829        let mock = server.mock(|when, then| {
1830            when.path("/indexes/index-name");
1831            then.status(500);
1832        });
1833
1834        let config = PineconeClientConfig {
1835            api_key: Some("api_key".to_string()),
1836            control_plane_host: Some(server.base_url()),
1837            ..Default::default()
1838        };
1839        let pinecone = config.client().expect("Failed to create Pinecone instance");
1840
1841        let configure_index_response = pinecone
1842            .configure_index(
1843                "index-name",
1844                Some(DeletionProtection::Enabled),
1845                Some(6),
1846                Some("p1.x1"),
1847            )
1848            .await
1849            .expect_err("Expected to fail to configure index");
1850
1851        assert!(matches!(
1852            configure_index_response,
1853            PineconeError::InternalServerError { .. }
1854        ));
1855
1856        mock.assert();
1857
1858        Ok(())
1859    }
1860
1861    #[tokio::test]
1862    async fn test_delete_index() -> Result<(), PineconeError> {
1863        let server = MockServer::start();
1864
1865        let mock = server.mock(|when, then| {
1866            when.method(DELETE).path("/indexes/index-name");
1867            then.status(202);
1868        });
1869
1870        let config = PineconeClientConfig {
1871            api_key: Some("api_key".to_string()),
1872            control_plane_host: Some(server.base_url()),
1873            ..Default::default()
1874        };
1875        let pinecone = config.client().expect("Failed to create Pinecone instance");
1876
1877        let _ = pinecone
1878            .delete_index("index-name")
1879            .await
1880            .expect("Failed to delete index");
1881
1882        mock.assert();
1883
1884        Ok(())
1885    }
1886
1887    #[tokio::test]
1888    async fn test_delete_index_invalid_name() -> Result<(), PineconeError> {
1889        let server = MockServer::start();
1890
1891        let mock = server.mock(|when, then| {
1892            when.method(DELETE).path("/indexes/invalid-index");
1893            then.status(404)
1894                .header("content-type", "application/json")
1895                .body(
1896                    r#"
1897                    {
1898                        "error": "Index not found"
1899                    }
1900                "#,
1901                );
1902        });
1903
1904        let config = PineconeClientConfig {
1905            api_key: Some("api_key".to_string()),
1906            control_plane_host: Some(server.base_url()),
1907            ..Default::default()
1908        };
1909        let pinecone = config.client().expect("Failed to create Pinecone instance");
1910
1911        let delete_index_response = pinecone
1912            .delete_index("invalid-index")
1913            .await
1914            .expect_err("Expected delete_index to return an error");
1915
1916        assert!(matches!(
1917            delete_index_response,
1918            PineconeError::IndexNotFoundError { .. }
1919        ));
1920
1921        mock.assert();
1922
1923        Ok(())
1924    }
1925
1926    #[tokio::test]
1927    async fn test_delete_index_pending_collection() -> Result<(), PineconeError> {
1928        let server = MockServer::start();
1929
1930        let mock = server.mock(|when, then| {
1931            when.method(DELETE).path("/indexes/index-name");
1932            then.status(412);
1933        });
1934
1935        let config = PineconeClientConfig {
1936            api_key: Some("api_key".to_string()),
1937            control_plane_host: Some(server.base_url()),
1938            ..Default::default()
1939        };
1940        let pinecone = config.client().expect("Failed to create Pinecone instance");
1941
1942        let delete_index_response = pinecone
1943            .delete_index("index-name")
1944            .await
1945            .expect_err("Expected delete_index to return an error");
1946
1947        assert!(matches!(
1948            delete_index_response,
1949            PineconeError::PendingCollectionError { .. }
1950        ));
1951
1952        mock.assert();
1953
1954        Ok(())
1955    }
1956
1957    #[tokio::test]
1958    async fn test_delete_index_server_error() -> Result<(), PineconeError> {
1959        let server = MockServer::start();
1960
1961        let mock = server.mock(|when, then| {
1962            when.method(DELETE).path("/indexes/index-name");
1963            then.status(500);
1964        });
1965
1966        let config = PineconeClientConfig {
1967            api_key: Some("api_key".to_string()),
1968            control_plane_host: Some(server.base_url()),
1969            ..Default::default()
1970        };
1971        let pinecone = config.client().expect("Failed to create Pinecone instance");
1972
1973        let delete_index_response = pinecone
1974            .delete_index("index-name")
1975            .await
1976            .expect_err("Expected delete_index to return an error");
1977
1978        assert!(matches!(
1979            delete_index_response,
1980            PineconeError::InternalServerError { .. }
1981        ));
1982
1983        mock.assert();
1984
1985        Ok(())
1986    }
1987
1988    #[tokio::test]
1989    async fn test_create_collection() -> Result<(), PineconeError> {
1990        let server = MockServer::start();
1991
1992        let mock = server.mock(|when, then| {
1993            when.method(POST).path("/collections");
1994            then.status(201)
1995                .header("content-type", "application/json")
1996                .body(
1997                    r#"
1998                    {
1999                        "name": "example-collection",
2000                        "size": 10000000,
2001                        "status": "Initializing",
2002                        "dimension": 1536,
2003                        "vector_count": 120000,
2004                        "environment": "us-east1-gcp"
2005                    }
2006                    "#,
2007                );
2008        });
2009
2010        // Construct Pinecone instance with the mock server URL
2011        let config = PineconeClientConfig {
2012            api_key: Some("api_key".to_string()),
2013            control_plane_host: Some(server.base_url()),
2014            ..Default::default()
2015        };
2016        let pinecone = config.client().expect("Failed to create Pinecone instance");
2017
2018        // Call create_collection and verify the result
2019        let collection = pinecone
2020            .create_collection("collection1", "index1")
2021            .await
2022            .expect("Failed to create collection");
2023
2024        let expected = CollectionModel {
2025            name: "example-collection".to_string(),
2026            size: Some(10000000),
2027            status: Status::Initializing,
2028            dimension: Some(1536),
2029            vector_count: Some(120000),
2030            environment: "us-east1-gcp".to_string(),
2031        };
2032        assert_eq!(collection, expected);
2033
2034        mock.assert();
2035
2036        Ok(())
2037    }
2038
2039    #[tokio::test]
2040    async fn test_create_collection_quota_exceeded() -> Result<(), PineconeError> {
2041        let server = MockServer::start();
2042
2043        let mock = server.mock(|when, then| {
2044            when.method(POST).path("/collections");
2045            then.status(403)
2046                .header("content-type", "application/json")
2047                .body(
2048                    r#"
2049                    {
2050                        "error": {
2051                            "code": "FORBIDDEN",
2052                            "message": "Collection exceeds quota. Maximum allowed on your account is 1. Currently have 1."
2053                        },
2054                        "status": 403
2055                    }
2056                "#,
2057                );
2058        });
2059
2060        let config = PineconeClientConfig {
2061            api_key: Some("api_key".to_string()),
2062            control_plane_host: Some(server.base_url()),
2063            ..Default::default()
2064        };
2065        let pinecone = config.client().expect("Failed to create Pinecone instance");
2066
2067        let create_collection_response = pinecone
2068            .create_collection("invalid_collection", "valid-index")
2069            .await
2070            .expect_err("Expected create_collection to return an error");
2071
2072        assert!(matches!(
2073            create_collection_response,
2074            PineconeError::CollectionsQuotaExceededError { .. }
2075        ));
2076
2077        mock.assert();
2078
2079        Ok(())
2080    }
2081
2082    #[tokio::test]
2083    async fn test_create_collection_invalid_name() -> Result<(), PineconeError> {
2084        let server = MockServer::start();
2085
2086        let mock = server.mock(|when, then| {
2087            when.method(POST).path("/collections");
2088            then.status(409)
2089                .header("content-type", "application/json")
2090                .body(
2091                    r#"
2092                    {
2093                        "error": "Index not found"
2094                    }
2095                "#,
2096                );
2097        });
2098
2099        let config = PineconeClientConfig {
2100            api_key: Some("api_key".to_string()),
2101            control_plane_host: Some(server.base_url()),
2102            ..Default::default()
2103        };
2104        let pinecone = config.client().expect("Failed to create Pinecone instance");
2105
2106        let create_collection_response = pinecone
2107            .create_collection("invalid_collection", "valid-index")
2108            .await
2109            .expect_err("Expected create_collection to return an error");
2110
2111        assert!(matches!(
2112            create_collection_response,
2113            PineconeError::ResourceAlreadyExistsError { .. }
2114        ));
2115
2116        mock.assert();
2117
2118        Ok(())
2119    }
2120
2121    #[tokio::test]
2122    async fn test_create_collection_server_error() -> Result<(), PineconeError> {
2123        let server = MockServer::start();
2124
2125        let mock = server.mock(|when, then| {
2126            when.method(POST).path("/collections");
2127            then.status(500);
2128        });
2129
2130        let config = PineconeClientConfig {
2131            api_key: Some("api_key".to_string()),
2132            control_plane_host: Some(server.base_url()),
2133            ..Default::default()
2134        };
2135        let pinecone = config.client().expect("Failed to create Pinecone instance");
2136
2137        let create_collection_response = pinecone
2138            .create_collection("collection-name", "index1")
2139            .await
2140            .expect_err("Expected create_collection to return an error");
2141
2142        assert!(matches!(
2143            create_collection_response,
2144            PineconeError::InternalServerError { .. }
2145        ));
2146
2147        mock.assert();
2148
2149        Ok(())
2150    }
2151
2152    #[tokio::test]
2153    async fn test_describe_collection() -> Result<(), PineconeError> {
2154        let server = MockServer::start();
2155
2156        let mock = server.mock(|when, then| {
2157            when.method(GET).path("/collections/collection-name");
2158            then.status(200)
2159                .header("content-type", "application/json")
2160                .body(
2161                    r#"{
2162                        "dimension": 3,
2163                        "environment": "us-east1-gcp",
2164                        "name": "tiny-collection",
2165                        "size": 3126700,
2166                        "status": "Ready",
2167                        "vector_count": 99
2168                      }"#,
2169                );
2170        });
2171
2172        // Construct Pinecone instance with the mock server URL
2173        let config = PineconeClientConfig {
2174            api_key: Some("api_key".to_string()),
2175            control_plane_host: Some(server.base_url()),
2176            ..Default::default()
2177        };
2178        let pinecone = config.client().expect("Failed to create Pinecone instance");
2179
2180        // Call describe_collection and verify the result
2181        let collection = pinecone
2182            .describe_collection("collection-name")
2183            .await
2184            .expect("Failed to describe collection");
2185
2186        let expected = CollectionModel {
2187            name: "tiny-collection".to_string(),
2188            size: Some(3126700),
2189            status: Status::Ready,
2190            dimension: Some(3),
2191            vector_count: Some(99),
2192            environment: "us-east1-gcp".to_string(),
2193        };
2194
2195        assert_eq!(collection, expected);
2196        mock.assert();
2197
2198        Ok(())
2199    }
2200
2201    #[tokio::test]
2202    async fn test_describe_collection_invalid_name() -> Result<(), PineconeError> {
2203        let server = MockServer::start();
2204
2205        let mock = server.mock(|when, then| {
2206            when.method(GET).path("/collections/invalid-collection");
2207            then.status(404)
2208                .header("content-type", "application/json")
2209                .body(
2210                    r#"{
2211                    "error": "Collection invalid-collection not found"
2212                }"#,
2213                );
2214        });
2215
2216        let config = PineconeClientConfig {
2217            api_key: Some("api_key".to_string()),
2218            control_plane_host: Some(server.base_url()),
2219            ..Default::default()
2220        };
2221        let pinecone = config.client().expect("Failed to create Pinecone instance");
2222
2223        let response = pinecone
2224            .describe_collection("invalid-collection")
2225            .await
2226            .expect_err("Expected describe_collection to return an error");
2227
2228        assert!(matches!(
2229            response,
2230            PineconeError::CollectionNotFoundError { .. }
2231        ));
2232        mock.assert();
2233
2234        Ok(())
2235    }
2236
2237    #[tokio::test]
2238    async fn test_describe_collection_server_error() -> Result<(), PineconeError> {
2239        let server = MockServer::start();
2240
2241        let mock = server.mock(|when, then| {
2242            when.method(GET).path("/collections/collection-name");
2243            then.status(500);
2244        });
2245
2246        let config = PineconeClientConfig {
2247            api_key: Some("api_key".to_string()),
2248            control_plane_host: Some(server.base_url()),
2249            ..Default::default()
2250        };
2251        let pinecone = config.client().expect("Failed to create Pinecone instance");
2252
2253        let response = pinecone
2254            .describe_collection("collection-name")
2255            .await
2256            .expect_err("Expected describe_collection to return an error");
2257
2258        assert!(matches!(
2259            response,
2260            PineconeError::InternalServerError { .. }
2261        ));
2262        mock.assert();
2263
2264        Ok(())
2265    }
2266
2267    #[tokio::test]
2268    async fn test_list_collections() -> Result<(), PineconeError> {
2269        let server = MockServer::start();
2270
2271        let mock = server.mock(|when, then| {
2272            when.method(GET).path("/collections");
2273            then.status(200)
2274                .header("content-type", "application/json")
2275                .body(
2276                    r#"
2277                    {
2278                        "collections": [
2279                            {
2280                                "name": "small-collection",
2281                                "size": 3126700,
2282                                "status": "Ready",
2283                                "dimension": 3,
2284                                "vector_count": 99,
2285                                "environment": "us-east1-gcp"
2286                            },
2287                            {
2288                                "name": "small-collection-new",
2289                                "size": 3126700,
2290                                "status": "Initializing",
2291                                "dimension": 3,
2292                                "vector_count": 99,
2293                                "environment": "us-east1-gcp"
2294                            },
2295                            {
2296                                "name": "big-collection",
2297                                "size": 160087040000000,
2298                                "status": "Ready",
2299                                "dimension": 1536,
2300                                "vector_count": 10000000,
2301                                "environment": "us-east1-gcp"
2302                            }
2303                        ]
2304                    }"#,
2305                );
2306        });
2307
2308        // Construct Pinecone instance with the mock server URL
2309        let config = PineconeClientConfig {
2310            api_key: Some("api_key".to_string()),
2311            control_plane_host: Some(server.base_url()),
2312            ..Default::default()
2313        };
2314        let pinecone = config.client().expect("Failed to create Pinecone instance");
2315
2316        // Call list_collections and verify the result
2317        let collection_list = pinecone
2318            .list_collections()
2319            .await
2320            .expect("Failed to list collections");
2321
2322        let expected = CollectionList {
2323            // name: String, dimension: i32, metric: Metric, host: String, spec: models::IndexModelSpec, status: models::IndexModelStatus)
2324            collections: Some(vec![
2325                CollectionModel {
2326                    name: "small-collection".to_string(),
2327                    size: Some(3126700),
2328                    status: Status::Ready,
2329                    dimension: Some(3),
2330                    vector_count: Some(99),
2331                    environment: "us-east1-gcp".to_string(),
2332                },
2333                CollectionModel {
2334                    name: "small-collection-new".to_string(),
2335                    size: Some(3126700),
2336                    status: Status::Initializing,
2337                    dimension: Some(3),
2338                    vector_count: Some(99),
2339                    environment: "us-east1-gcp".to_string(),
2340                },
2341                CollectionModel {
2342                    name: "big-collection".to_string(),
2343                    size: Some(160087040000000),
2344                    status: Status::Ready,
2345                    dimension: Some(1536),
2346                    vector_count: Some(10000000),
2347                    environment: "us-east1-gcp".to_string(),
2348                },
2349            ]),
2350        };
2351        assert_eq!(collection_list, expected);
2352
2353        mock.assert();
2354
2355        Ok(())
2356    }
2357
2358    #[tokio::test]
2359    async fn test_list_collections_error() -> Result<(), PineconeError> {
2360        let server = MockServer::start();
2361
2362        let mock = server.mock(|when, then| {
2363            when.method(GET).path("/collections");
2364            then.status(500);
2365        });
2366
2367        let config = PineconeClientConfig {
2368            api_key: Some("api_key".to_string()),
2369            control_plane_host: Some(server.base_url()),
2370            ..Default::default()
2371        };
2372        let pinecone = config.client().expect("Failed to create Pinecone instance");
2373
2374        // Call list_collections and verify the result
2375        let list_collections_response = pinecone
2376            .list_collections()
2377            .await
2378            .expect_err("Expected to fail to list collections");
2379
2380        assert!(matches!(
2381            list_collections_response,
2382            PineconeError::InternalServerError { .. }
2383        ));
2384        mock.assert();
2385
2386        Ok(())
2387    }
2388
2389    #[tokio::test]
2390    async fn test_delete_collection() -> Result<(), PineconeError> {
2391        let server = MockServer::start();
2392
2393        let mock = server.mock(|when, then| {
2394            when.method(DELETE).path("/collections/collection-name");
2395            then.status(202);
2396        });
2397
2398        let config = PineconeClientConfig {
2399            api_key: Some("api_key".to_string()),
2400            control_plane_host: Some(server.base_url()),
2401            ..Default::default()
2402        };
2403        let pinecone = config.client().expect("Failed to create Pinecone instance");
2404
2405        let _ = pinecone
2406            .delete_collection("collection-name")
2407            .await
2408            .expect("Failed to delete collection");
2409
2410        mock.assert();
2411
2412        Ok(())
2413    }
2414
2415    #[tokio::test]
2416    async fn test_delete_collection_not_found() -> Result<(), PineconeError> {
2417        let server = MockServer::start();
2418
2419        let mock = server.mock(|when, then| {
2420            when.method(DELETE).path("/collections/collection-name");
2421            then.status(404)
2422                .header("content-type", "application/json")
2423                .body(
2424                    r#"
2425                    {
2426                        "error": "Collection not found"
2427                    }
2428                "#,
2429                );
2430        });
2431
2432        let config = PineconeClientConfig {
2433            api_key: Some("api_key".to_string()),
2434            control_plane_host: Some(server.base_url()),
2435            ..Default::default()
2436        };
2437        let pinecone = config.client().expect("Failed to create Pinecone instance");
2438
2439        let delete_collection_response = pinecone
2440            .delete_collection("collection-name")
2441            .await
2442            .expect_err("Expected delete_collection to return an error");
2443
2444        assert!(matches!(
2445            delete_collection_response,
2446            PineconeError::CollectionNotFoundError { .. }
2447        ));
2448
2449        mock.assert();
2450
2451        Ok(())
2452    }
2453
2454    #[tokio::test]
2455    async fn test_delete_collection_internal_error() -> Result<(), PineconeError> {
2456        let server = MockServer::start();
2457
2458        let mock = server.mock(|when, then| {
2459            when.method(DELETE).path("/collections/collection-name");
2460            then.status(500);
2461        });
2462
2463        let config = PineconeClientConfig {
2464            api_key: Some("api_key".to_string()),
2465            control_plane_host: Some(server.base_url()),
2466            ..Default::default()
2467        };
2468        let pinecone = config.client().expect("Failed to create Pinecone instance");
2469
2470        let delete_collection_response = pinecone
2471            .delete_collection("collection-name")
2472            .await
2473            .expect_err("Expected delete_collection to return an error");
2474
2475        assert!(matches!(
2476            delete_collection_response,
2477            PineconeError::InternalServerError { .. }
2478        ));
2479
2480        mock.assert();
2481
2482        Ok(())
2483    }
2484}