blob: cbaa76da31264e59ee107e5e91985a4a143d8bcf [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
use maka_agent_facts::pricing::Entry;
use maka_agent_facts::pricing::Mutation;
use maka_agent_facts::pricing::Page;
use maka_agent_facts::pricing::Pricing;
use maka_agent_facts::pricing::Query;
use maka_agent_facts::pricing::ResetEffect;
use maka_agent_facts::pricing::Update;
use maka_agent_facts::pricing::Updated;
use maka_config::ConfigurationStore;
use maka_event_log::root::{RootNamespaces, RootOwner};
use std::sync::Arc;
async fn entries(store: &ConfigurationStore) -> (u64, Vec<Entry>) {
let mut query = Query::Start;
let mut all = Vec::new();
loop {
let page = store.query_pricing(query).await.unwrap();
let page_bytes = serde_json::to_vec(&page).unwrap().len();
let Page::Page {
revision,
offset,
entries,
next_offset,
} = page
else {
panic!("stable snapshot")
};
assert_eq!(offset, all.len() as u64);
assert!(entries.len() <= 128);
assert!(page_bytes <= 48 * 1024 || entries.len() == 1);
if let Some(next) = next_offset {
assert!(next > offset);
}
all.extend(entries);
match next_offset {
Some(offset) => query = Query::Continue { revision, offset },
None => return (revision, all),
}
}
}
fn price(entry: &Entry) -> &Pricing {
match entry {
Entry::Builtin { pricing } | Entry::Custom { pricing, .. } => pricing,
}
}
async fn quote(store: &ConfigurationStore, key: &str) -> (u64, Option<Pricing>) {
let (provider, model) = key.split_once(':').unwrap();
let quote = store
.quote_model(provider.into(), model.into())
.await
.unwrap();
(quote.revision, quote.pricing)
}
#[tokio::test]
async fn pricing_cas_pages_and_quotes_survive_reopen_and_bundled_rate_replacement() {
let temp = tempfile::tempdir().unwrap();
let owner = Arc::new(
RootOwner::create(
&temp.path().join("root"),
&RootNamespaces {
ownership: temp.path().join("owners"),
control: temp.path().join("control"),
},
)
.unwrap(),
);
let store = ConfigurationStore::for_root(owner.clone()).await.unwrap();
let (revision, initial) = entries(&store).await;
assert_eq!(revision, 0);
assert!(
initial.len() > 128,
"exercise pagination against the real offline catalog"
);
let original = price(&initial[0]).clone();
let mut custom = original.clone();
custom.input_usd_per_million += 10.0;
let update = Update {
expected_revision: 0,
mutation: Mutation::Upsert {
pricing: custom.clone(),
},
};
assert_eq!(
store.update_pricing(update.clone()).await.unwrap(),
Updated::Committed { revision: 1 }
);
// A lost mutation reply cannot overwrite a later state on retry.
assert_eq!(
store.update_pricing(update).await.unwrap(),
Updated::RevisionConflict {
expected_revision: 0,
actual_revision: 1
}
);
assert!(matches!(
store
.query_pricing(Query::Continue {
revision: 0,
offset: 128
})
.await
.unwrap(),
Page::RevisionChanged {
expected_revision: 0,
actual_revision: 1
}
));
assert_eq!(
store
.update_pricing(Update {
expected_revision: 1,
mutation: Mutation::Upsert {
pricing: custom.clone()
},
})
.await
.unwrap(),
Updated::Unchanged { revision: 1 }
);
let captured = quote(&store, &custom.model_key).await;
assert_eq!(captured, (1, Some(custom.clone())));
assert!(entries(&store).await.1.iter().any(|entry| *entry
== Entry::Custom {
pricing: custom.clone(),
reset_effect: ResetEffect::RestoreBuiltin,
}));
// Supplementary-plane keys sort before U+E000 in JS, unlike UTF-8 ordering.
let mut revision = 1;
for key in ["fixture:\u{e000}", "fixture:\u{10000}"] {
revision += 1;
assert_eq!(
store
.update_pricing(Update {
expected_revision: revision - 1,
mutation: Mutation::Upsert {
pricing: Pricing {
model_key: key.into(),
..custom.clone()
}
},
})
.await
.unwrap(),
Updated::Committed { revision }
);
}
let (_, all) = entries(&store).await;
assert!(all.windows(2).all(|pair| {
price(&pair[0])
.model_key
.encode_utf16()
.cmp(price(&pair[1]).model_key.encode_utf16())
.is_lt()
}));
assert!(
all.iter()
.filter(|entry| matches!(
entry,
Entry::Custom {
reset_effect: ResetEffect::BecomeUnpriced,
..
}
))
.count()
>= 2
);
assert!(
store
.query_pricing(Query::Continue {
revision,
offset: all.len() as u64
})
.await
.is_err()
);
store.close().await.unwrap();
let store = ConfigurationStore::for_root(owner.clone()).await.unwrap();
assert_eq!(
quote(&store, &custom.model_key).await,
(3, Some(custom.clone()))
);
assert_eq!(
store
.update_pricing(Update {
expected_revision: 3,
mutation: Mutation::Delete {
model_key: custom.model_key.clone()
}
})
.await
.unwrap(),
Updated::Committed { revision: 4 }
);
assert_eq!(quote(&store, &custom.model_key).await, (4, Some(original)));
assert_eq!(
captured,
(1, Some(custom)),
"captured quote is not a mutable pricing reference"
);
assert_eq!(
store
.update_pricing(Update {
expected_revision: 4,
mutation: Mutation::Delete {
model_key: "missing:model".into()
}
})
.await
.unwrap(),
Updated::Unchanged { revision: 4 }
);
store.close().await.unwrap();
// Emulate a binary built from another bundled-rate version; keep overrides.
let db = rusqlite::Connection::open(owner.canonical_path().join("configuration-rust.sqlite"))
.unwrap();
db.execute(
"UPDATE pricing_authority SET builtin_digest = 'previous-build'",
[],
)
.unwrap();
drop(db);
let store = ConfigurationStore::for_root(owner).await.unwrap();
assert!(matches!(
store
.query_pricing(Query::Continue {
revision: 4,
offset: 1
})
.await
.unwrap(),
Page::RevisionChanged {
actual_revision: 5,
..
}
));
assert_eq!(quote(&store, "fixture:\u{e000}").await.0, 5);
store.close().await.unwrap();
}
#[tokio::test]
async fn pricing_pages_preserve_one_large_record_and_continue_after_reopen() {
let temp = tempfile::tempdir().unwrap();
let owner = Arc::new(
RootOwner::create(
&temp.path().join("root"),
&RootNamespaces {
ownership: temp.path().join("owners"),
control: temp.path().join("control"),
},
)
.unwrap(),
);
let store = ConfigurationStore::for_root(owner.clone()).await.unwrap();
let pricing = Pricing {
model_key: format!("example:{}", "route-model-".repeat(12_000)),
input_usd_per_million: 1.25,
output_usd_per_million: 3.5,
cache_read_usd_per_million: None,
cache_write_usd_per_million: None,
};
assert_eq!(
store
.update_pricing(Update {
expected_revision: 0,
mutation: Mutation::Upsert {
pricing: pricing.clone()
}
})
.await
.unwrap(),
Updated::Committed { revision: 1 }
);
store.close().await.unwrap();
let store = ConfigurationStore::for_root(owner).await.unwrap();
assert_eq!(
quote(&store, &pricing.model_key).await,
(1, Some(pricing.clone()))
);
let (revision, rows) = entries(&store).await;
assert_eq!(revision, 1);
assert_eq!(
rows.iter()
.filter(|row| price(row).model_key == pricing.model_key)
.map(price)
.collect::<Vec<_>>(),
vec![&pricing]
);
assert!(
rows.iter()
.any(|row| price(row).model_key != pricing.model_key)
);
store.close().await.unwrap();
}