Skip to content

Commit

Permalink
Add more unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
conradkleinespel committed Nov 3, 2024
1 parent f602bbb commit 9aa16c9
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 41 deletions.
103 changes: 70 additions & 33 deletions libm8s/src/file_format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use indexmap::{indexmap, IndexMap};
use schemars::JsonSchema;
use serde::Deserialize;
use std::collections::HashSet;
use std::io::Error;
use std::{fs, io};

#[derive(Debug, Deserialize, PartialEq, Clone, JsonSchema)]
Expand Down Expand Up @@ -92,8 +93,11 @@ pub fn create_json_schema() -> io::Result<String> {
.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))
}

/// Looks for cycles using a depth-first approach
/// See: https://en.wikipedia.org/wiki/Depth-first_search#Pseudocode
#[test]
fn test_create_json_schema_returns_json_schema() {
assert!(create_json_schema().unwrap().contains("\"$schema\":"))
}

fn analyse_cycles(
resource_key: &String,
dependencies_by_resource_key: &IndexMap<String, Vec<String>>,
Expand Down Expand Up @@ -180,19 +184,23 @@ pub fn check_dependency_cycles(
&mut HashSet::new(),
&mut Vec::new(),
) {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"Configuration is invalid, dependency cycle for \"{}\": {}",
resource_key,
cycle.join(" -> ")
),
));
return Err(create_dependency_cycle_error(resource_key, cycle));
}
}
Ok(())
}

fn create_dependency_cycle_error(resource_key: &String, cycle: Vec<String>) -> Error {
io::Error::new(
io::ErrorKind::InvalidData,
format!(
"Configuration is invalid, dependency cycle for \"{}\": {}",
resource_key,
cycle.join(" -> ")
),
)
}

fn create_file_not_exists_error(resource_key: &str, path: &str) -> io::Error {
io::Error::new(
io::ErrorKind::InvalidData,
Expand Down Expand Up @@ -221,39 +229,28 @@ pub fn check_helm_remote_repositories(
match resource {
Resource::HelmRemote { helm_remote } => match helm_remote.chart_name.split_once("/") {
None => {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"Invalid resource {}, chart name \"{}\" doesn't start with a repository name",
resource_key, helm_remote.chart_name
),
return Err(create_invalid_helm_chart_name_error(
resource_key,
helm_remote,
))
}
Some((repository_name,_)) => match helm_repositories {
Some((repository_name, _)) => match helm_repositories {
None => {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"Invalid resource {}, repository with name \"{}\" doesn't exist, no repositories configured",
resource_key, repository_name
),
return Err(create_helm_no_repositories_error(
resource_key,
repository_name,
))
}
Some(helm_repositories) => {
if helm_repositories
if let None = helm_repositories
.iter()
.filter(|r| r.name == repository_name)
.next()
.is_none()
{
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"Invalid resource {}, repository with name \"{}\" doesn't exist, valid values are [{}]",
resource_key,
repository_name,
helm_repositories.iter().map(|r| r.name.clone()).collect::<Vec<String>>().join(", ")
),
return Err(create_helm_repository_not_exists_error(
resource_key,
repository_name,
helm_repositories,
));
}
}
Expand All @@ -265,6 +262,46 @@ pub fn check_helm_remote_repositories(
Ok(())
}

fn create_invalid_helm_chart_name_error(resource_key: &String, helm_remote: &HelmRemote) -> Error {
io::Error::new(
io::ErrorKind::InvalidData,
format!(
"Invalid resource {}, chart name \"{}\" doesn't start with a repository name",
resource_key, helm_remote.chart_name
),
)
}

fn create_helm_no_repositories_error(resource_key: &String, repository_name: &str) -> Error {
io::Error::new(
io::ErrorKind::InvalidData,
format!(
"Invalid resource {}, repository with name \"{}\" doesn't exist, no repositories configured",
resource_key, repository_name
),
)
}

fn create_helm_repository_not_exists_error(
resource_key: &String,
repository_name: &str,
helm_repositories: &Vec<HelmRepository>,
) -> Error {
io::Error::new(
io::ErrorKind::InvalidData,
format!(
"Invalid resource {}, repository with name \"{}\" doesn't exist, valid values are [{}]",
resource_key,
repository_name,
helm_repositories
.iter()
.map(|r| r.name.clone())
.collect::<Vec<String>>()
.join(", ")
),
)
}

pub fn check_files_exist(resources: &IndexMap<String, ResourceWithDepdencies>) -> io::Result<()> {
for (resource_key, ResourceWithDepdencies { resource, .. }) in resources {
match resource {
Expand Down
93 changes: 90 additions & 3 deletions libm8s/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,29 @@ pub mod helm_repositories;
pub mod resources;
pub mod utils;

pub fn parse_deployment_file(deployment_file_path: &str) -> io::Result<Config> {
info!("Deploying from {}...", deployment_file_path);
trait FileReader {
fn read_to_string(&self, file_path: &Path) -> io::Result<String>;
}

struct NativeFileReader;

impl FileReader for NativeFileReader {
fn read_to_string(&self, file_path: &Path) -> io::Result<String> {
fs::read_to_string(file_path)
}
}

pub fn parse_deployment_file(deployment_file_path: &Path) -> io::Result<Config> {
parse_deployment_file_with_reader(NativeFileReader {}, deployment_file_path)
}

let yaml_data = match fs::read_to_string(deployment_file_path) {
fn parse_deployment_file_with_reader(
file_reader: impl FileReader,
deployment_file_path: &Path,
) -> io::Result<Config> {
info!("Deploying from {:?}...", deployment_file_path);

let yaml_data = match file_reader.read_to_string(deployment_file_path) {
Err(err) => {
return Err(io::Error::new(
err.kind(),
Expand Down Expand Up @@ -44,6 +63,74 @@ pub fn parse_deployment_file(deployment_file_path: &str) -> io::Result<Config> {
Ok(config)
}

#[cfg(test)]
mod test {
use crate::file_format::Resource::{Group, HelmLocal, HelmRemote, Manifest};
use crate::{parse_deployment_file_with_reader, FileReader};
use std::path::Path;

struct MockFileReader;

impl FileReader for MockFileReader {
fn read_to_string(&self, _: &Path) -> std::io::Result<String> {
let config_file = include_str!("../tests/m8s_only_resources.yaml");
Ok(config_file.to_string())
}
}

#[test]
fn test_parse_deployment_file_with_reader_returns_config() {
let config = parse_deployment_file_with_reader(
MockFileReader {},
&Path::new("/my/m8s/dir/m8s.yaml"),
)
.unwrap();

match &config.resources.get("foobarManifest").unwrap().resource {
Manifest { manifest } => {
assert_eq!("/my/m8s/dir/path/to/manifest.yaml", manifest.path.as_str());
}
_ => panic!("Expected Ressource::Manifest"),
}

match &config.resources.get("foobarHelmLocal").unwrap().resource {
HelmLocal { helm_local } => {
assert_eq!(
"/my/m8s/dir/values-local.yaml",
helm_local.values.as_ref().unwrap().get(0).unwrap().as_str()
);
}
_ => panic!("Expected Ressource::HelmLocal"),
}

match &config.resources.get("foobarHelmRemote").unwrap().resource {
HelmRemote { helm_remote } => {
assert_eq!(
"/my/m8s/dir/values-remote.yaml",
helm_remote
.values
.as_ref()
.unwrap()
.get(0)
.unwrap()
.as_str()
);
}
_ => panic!("Expected Ressource::HelmRemote"),
}

match &config.resources.get("foobarGroup").unwrap().resource {
Group { group } => match &group.get("subFoobarManifest").unwrap().resource {
Manifest { manifest } => {
assert_eq!("/my/m8s/dir/sub-manifest.yaml", manifest.path.as_str());
}
_ => panic!("Expected Ressource::Manifest"),
},
_ => panic!("Expected Ressource::Group"),
}
}
}

fn integrate_deployment_file_dir_into_paths(
resources: &mut IndexMap<String, ResourceWithDepdencies>,
deployment_file_dir: PathBuf,
Expand Down
9 changes: 5 additions & 4 deletions libm8s/tests/m8s_only_resources.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ resources:
namespace: test-namespace
chartPath: path/to/chart
values:
- values.yaml
- values-local.yaml
foobarHelmLocalNoValues:
helmLocal:
name: test-name
Expand All @@ -28,7 +28,7 @@ resources:
chartName: chart/name
chartVersion: 1.33.7
values:
- values.yaml
- values-remote.yaml
foobarHelmRemoteNoValues:
helmRemote:
name: test-name
Expand All @@ -37,5 +37,6 @@ resources:
chartVersion: 1.33.7
foobarGroup:
group:
foobarGroupNoop:
noop: ""
subFoobarManifest:
manifest:
path: sub-manifest.yaml
6 changes: 6 additions & 0 deletions libm8s/tests/manifest.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: test-ns
spec: {}
3 changes: 2 additions & 1 deletion libm8scmd/src/command_up.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::utils::CommandRunner;
use crate::{OptionDependencies, OptionHelmRepositories, OptionResources};
use std::io;
use std::path::Path;

pub struct CommandUp {
pub resources_args: Vec<String>,
Expand Down Expand Up @@ -34,7 +35,7 @@ impl CommandRunner for CommandUp {
}

let deployment_file_path = self.file.clone().unwrap_or("m8s.yaml".to_string());
let config = libm8s::parse_deployment_file(deployment_file_path.as_str())?;
let config = libm8s::parse_deployment_file(&Path::new(deployment_file_path.as_str()))?;

libm8s::file_format::check_resource_keys_format(&config.resources)?;
libm8s::file_format::check_invalid_resource_keys(&config.resources)?;
Expand Down

0 comments on commit 9aa16c9

Please sign in to comment.