Skip to content

Commit

Permalink
Implement storage component testing
Browse files Browse the repository at this point in the history
  • Loading branch information
broody committed Jun 2, 2023
1 parent 9ad3292 commit efb4d76
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 22 deletions.
28 changes: 17 additions & 11 deletions crates/torii/src/graphql/storage.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
use async_graphql::dynamic::{Field, FieldFuture, FieldValue, InputValue, TypeRef};
use async_graphql::Value;
use sqlx::pool::PoolConnection;
use sqlx::sqlite::SqliteRow;
use sqlx::{Error, Pool, Result, Row, Sqlite};

use super::{ObjectTrait, TypeMapping, ValueMapping};
use crate::graphql::types::ScalarType;

const BOOLEAN_TRUE: i64 = 1;

pub struct StorageObject {
pub name: String,
pub type_name: String,
Expand Down Expand Up @@ -52,9 +55,6 @@ impl ObjectTrait for StorageObject {
}
}

// Unlike other queries where we can take advantage of sqlx's static type checking,
// storage objects are dynamically generated at runtime so field type must be
// evaulated at runtime
async fn storage_by_id(
conn: &mut PoolConnection<Sqlite>,
name: &str,
Expand All @@ -63,24 +63,30 @@ async fn storage_by_id(
) -> Result<ValueMapping> {
let query = format!("SELECT * FROM storage_{} WHERE id = ?", name);
let storage = sqlx::query(&query).bind(id).fetch_one(conn).await?;
let result = value_mapping_from_row(&storage, fields)?;
Ok(result)
}

fn value_mapping_from_row(row: &SqliteRow, fields: &TypeMapping) -> Result<ValueMapping> {
let mut value_mapping = ValueMapping::new();

// Cairo's type data representation in sqlite db is str or int,
// for int max size is 64bit so store all other types as str
// int's max size is 64bit so we store all types above u64 as str
for (field_name, field_type) in fields {
let value = match field_type.as_str() {
ScalarType::U8
| ScalarType::U16
| ScalarType::U32
| ScalarType::U64
| TypeRef::BOOLEAN => {
let result = storage.try_get::<i64, &str>(field_name.as_str());
ScalarType::U8 | ScalarType::U16 | ScalarType::U32 | ScalarType::U64 => {
let result = row.try_get::<i64, &str>(field_name.as_str());
Value::from(result?)
}
ScalarType::U128 | ScalarType::U250 | ScalarType::U256 | ScalarType::FELT => {
let result = storage.try_get::<String, &str>(field_name.as_str());
let result = row.try_get::<String, &str>(field_name.as_str());
Value::from(result?)
}
TypeRef::BOOLEAN => {
// sqlite stores booleans as 0 or 1
let result = row.try_get::<i64, &str>(field_name.as_str());
Value::from(matches!(result?, BOOLEAN_TRUE))
}
_ => return Err(Error::TypeNotFound { type_name: field_type.clone() }),
};
value_mapping.insert(field_name.clone(), value);
Expand Down
34 changes: 30 additions & 4 deletions crates/torii/src/tests/components_test.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,37 @@
#[cfg(test)]
mod tests {
use serde::Deserialize;
use sqlx::SqlitePool;

use crate::graphql::schema::build_schema;
use crate::tests::common::run_graphql_query;

#[sqlx::test(migrations = "./migrations", fixtures("components"))]
async fn test_dynamic_component(pool: SqlitePool) {
let schema = build_schema(&pool).await.expect("failed to build schema");
#[derive(Deserialize)]
struct Game {
name: String,
is_finished: bool,
}

#[derive(Deserialize)]
struct Stats {
health: i64,
mana: i64,
}

#[sqlx::test(migrations = "./migrations", fixtures("entities", "components"))]
async fn test_storage_components(pool: SqlitePool) {
let _ = pool.acquire().await;

let query = "{ game(id: 1) { name is_finished } stats(id: 1) { health mana } }";
let value = run_graphql_query(&pool, query).await;

let game = value.get("game").ok_or("no game found").unwrap();
let game: Game = serde_json::from_value(game.clone()).unwrap();
let stats = value.get("stats").ok_or("no stats found").unwrap();
let stats: Stats = serde_json::from_value(stats.clone()).unwrap();

assert!(!game.is_finished);
assert_eq!(game.name, "0x594F4C4F");
assert_eq!(stats.health, 42);
assert_eq!(stats.mana, 69);
}
}
34 changes: 30 additions & 4 deletions crates/torii/src/tests/fixtures/components.sql
Original file line number Diff line number Diff line change
@@ -1,11 +1,37 @@
INSERT INTO components (id, name, address, class_hash, transaction_hash, storage_definition)
VALUES ('component_1', 'Game', '0x0', '0x0', '0x0',
'[{"name":"is_finished","type":"Boolean","slot":0,"offset":0}]');
'[{"name":"name","type":"FieldElement","slot":0,"offset":0},{"name":"is_finished","type":"Boolean","slot":0,"offset":0}]');

INSERT INTO components (id, name, address, class_hash, transaction_hash, storage_definition)
VALUES ('component_2', 'Stats', '0x0', '0x0', '0x0',
'[{"name":"health","type":"u8","slot":0,"offset":0},{"name":"mana","type":"u8","slot":0,"offset":0}]');

INSERT INTO components (id, name, address, class_hash, transaction_hash, storage_definition)
VALUES ('component_3', 'Cash', '0x0', '0x0', '0x0',
'[{"name":"amount","type":"u32","slot":0,"offset":0}]');

CREATE TABLE storage_game (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
is_finished BOOLEAN NOT NULL,
version TEXT NOT NULL,
entity_id TEXT NOT NULL,
component_id TEXT NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (entity_id) REFERENCES entities(id),
FOREIGN KEY (component_id) REFERENCES components(id)
);
CREATE TABLE storage_stats (
id INTEGER PRIMARY KEY AUTOINCREMENT,
health INTEGER NOT NULL,
mana INTEGER NOT NULL,
version TEXT NOT NULL,
entity_id TEXT NOT NULL,
component_id TEXT NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (entity_id) REFERENCES entities(id),
FOREIGN KEY (component_id) REFERENCES components(id)
);

INSERT INTO storage_game (id, name, is_finished, version, entity_id, component_id, created_at)
VALUES (1, '0x594F4C4F', 0, '0.0.0', 'entity_1', 'component_1', '2023-05-19T21:04:04Z');
INSERT INTO storage_stats (id, health, mana, version, entity_id, component_id, created_at)
VALUES (1, 42, 69, '0.0.0', 'entity_2', 'component_2', '2023-05-19T21:05:44Z');

7 changes: 4 additions & 3 deletions crates/torii/src/tests/fixtures/seed.sql
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ INSERT INTO indexer (head) VALUES (0);
/* register components and systems */
INSERT INTO components (id, name, address, class_hash, transaction_hash, storage_definition)
VALUES ('component_1', 'Game', '0x0', '0x0', '0x0',
'[{"name":"is_finished","type":"Boolean","slot":0,"offset":0}]');
'[{"name":"name","type":"FieldElement","slot":0,"offset":0},{"name":"is_finished","type":"Boolean","slot":0,"offset":0}]');
INSERT INTO components (id, name, address, class_hash, transaction_hash, storage_definition)
VALUES ('component_2', 'Stats', '0x0', '0x0', '0x0',
'[{"name":"health","type":"u8","slot":0,"offset":0},{"name":"mana","type":"u8","slot":0,"offset":0}]');
Expand Down Expand Up @@ -43,6 +43,7 @@ VALUES ( 'entity_3', 'Player', 'game_1', 'player_2', '0x0', '2023-05-19T21:08:12
/* tables for component storage, created at runtime by processor */
CREATE TABLE storage_game (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
is_finished BOOLEAN NOT NULL,
version TEXT NOT NULL,
entity_id TEXT NOT NULL,
Expand Down Expand Up @@ -73,8 +74,8 @@ CREATE TABLE storage_cash (
FOREIGN KEY (component_id) REFERENCES components(id)
);

INSERT INTO storage_game (id, is_finished, version, entity_id, component_id, created_at)
VALUES (1, 0, '0.0.0', 'entity_1', 'component_1', '2023-05-19T21:04:04Z');
INSERT INTO storage_game (id, name, is_finished, version, entity_id, component_id, created_at)
VALUES (1, '0x594F4C4F', 0, '0.0.0', 'entity_1', 'component_1', '2023-05-19T21:04:04Z');
INSERT INTO storage_stats (id, health, mana, version, entity_id, component_id, created_at)
VALUES (1, 100, 100, '0.0.0', 'entity_2', 'component_2', '2023-05-19T21:05:44Z');
INSERT INTO storage_stats (id, health, mana, version, entity_id, component_id, created_at)
Expand Down

0 comments on commit efb4d76

Please sign in to comment.