zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

factory.ex (14873B)


      1 # Zenflows is designed to implement the Valueflows vocabulary,
      2 # written and maintained by srfsh <info@dyne.org>.
      3 # Copyright (C) 2021-2023 Dyne.org foundation <foundation@dyne.org>.
      4 #
      5 # This program is free software: you can redistribute it and/or modify
      6 # it under the terms of the GNU Affero General Public License as published by
      7 # the Free Software Foundation, either version 3 of the License, or
      8 # (at your option) any later version.
      9 #
     10 # This program is distributed in the hope that it will be useful,
     11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
     12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13 # GNU Affero General Public License for more details.
     14 #
     15 # You should have received a copy of the GNU Affero General Public License
     16 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
     17 
     18 defmodule ZenflowsTest.Help.Factory do
     19 @moduledoc """
     20 Defines shortcuts for DB testing.
     21 """
     22 
     23 alias Zenflows.DB.Repo
     24 alias Zenflows.VF
     25 
     26 defdelegate id(), to: Zenflows.DB.ID, as: :gen
     27 
     28 @doc "Returns `DateTime.utc_now/0`."
     29 @spec now() :: DateTime.t()
     30 def now() do
     31 	DateTime.utc_now()
     32 end
     33 
     34 @doc "Like `now/0`, but piped to `DateTime.to_iso8601/1`."
     35 @spec iso_now() :: String.t()
     36 def iso_now() do
     37 	now() |> DateTime.to_iso8601()
     38 end
     39 
     40 @doc """
     41 Returns the same string with a unique positive integer attached at
     42 the end.
     43 """
     44 @spec str(String.t()) :: String.t()
     45 def str(s) do
     46 	"#{s}#{System.unique_integer([:positive])}"
     47 end
     48 
     49 @doc """
     50 Returns a list of string composed of one or ten items.  Each item is
     51 generated by piping `str` to `uniq/1`
     52 """
     53 @spec str_list(String.t(), non_neg_integer(), non_neg_integer()) :: [String.t()]
     54 def str_list(s, min \\ 1, max \\ 10) do
     55 	max = Enum.random(1..max)
     56 	Enum.map(min..max, fn _ -> str(s) end)
     57 end
     58 
     59 @doc """
     60 Returns a random integer between 0 (inclusive) and `max` (exclusive).
     61 """
     62 @spec int() :: integer()
     63 def int(max \\ 100) do
     64 	ceil(float(max))
     65 end
     66 
     67 @doc """
     68 Returns a random float between 0 (inclusive) and 1 (exclusive)
     69 multiplied by `mul`, which is 100 by default.
     70 """
     71 @spec float() :: float()
     72 def float(mul \\ 100) do
     73 	:rand.uniform() * mul
     74 end
     75 
     76 @doc """
     77 Returns a string that represents a `t:Decimal.t()` between 0
     78 (inclusive) and 1 (exclusive) multiplied by `mul`, which is 100 by
     79 default.
     80 """
     81 @spec decimal() :: String.t()
     82 def decimal(mul \\ 100) do
     83 	to_string(decimald(mul))
     84 end
     85 
     86 @doc """
     87 Returns a `t:Decimal.t()` between 0 (inclusive) and 1 (exclusive)
     88 multiplied by `mul`, which is 100 by default.
     89 """
     90 @spec decimald() :: Decimal.t()
     91 def decimald(mul \\ 100) do
     92 	Decimal.from_float(float(mul))
     93 end
     94 
     95 @doc "Returns a random boolean."
     96 @spec bool() :: boolean()
     97 def bool() do
     98 	:rand.uniform() < 0.5
     99 end
    100 
    101 @doc "Returns a unique URI string."
    102 @spec uri() :: String.t()
    103 def uri() do
    104 	str("schema://user@host:port/path")
    105 end
    106 
    107 @doc "Returns a file list."
    108 @spec file_list(non_neg_integer(), non_neg_integer()) :: [File.t()]
    109 def file_list(min \\ 1, max \\ 10) do
    110 	max = Enum.random(1..max)
    111 	Enum.map(min..max, fn _ ->
    112 		bin = str("some binary")
    113 		hash = :crypto.hash(:sha512, bin) |> Base.url_encode64(padding: false)
    114 		%Zenflows.File{
    115 			name: str("some name"),
    116 			description: str("some description"),
    117 			mime_type: str("some mime type"),
    118 			extension: str("some extension"),
    119 			signature: str("some signature"),
    120 			bin: bin,
    121 			hash: hash,
    122 			size: byte_size(bin),
    123 		}
    124 	end)
    125 end
    126 
    127 @doc "Inserts a schema into the database with field overrides."
    128 @spec insert!(atom(), %{required(atom()) => term()}) :: struct()
    129 def insert!(_, _ \\ %{})
    130 def insert!(:economic_event, _), do: insert_economic_event!()
    131 def insert!(:economic_resource, _), do: insert_economic_resource!()
    132 def insert!(name, params) do
    133 	name |> build!(params) |> Repo.insert!()
    134 end
    135 
    136 @doc "Builds a schema with field overrides."
    137 @spec build!(atom(), %{required(atom()) => term()}) :: struct()
    138 def build!(name, params \\ %{}) do
    139 	name |> build() |> struct!(params)
    140 end
    141 
    142 @doc """
    143 Like `build!/2`, but returns just a map.
    144 Useful for things like IDuration in the GraphQL spec.
    145 """
    146 @spec build_map!(atom()) :: map()
    147 def build_map!(name) do
    148 	build!(name)
    149 	|> Map.delete(:__struct__)
    150 	|> Map.delete(:__meta__)
    151 end
    152 
    153 def build(:time_unit) do
    154 	Enum.random(VF.TimeUnit.values())
    155 end
    156 
    157 def build(:iduration) do
    158 	%{
    159 		unit_type: build(:time_unit),
    160 		numeric_duration: decimald(),
    161 	}
    162 end
    163 
    164 def build(:unit) do
    165 	%VF.Unit{
    166 		label: str("some label"),
    167 		symbol: str("some symbol"),
    168 	}
    169 end
    170 
    171 def build(:imeasure) do
    172 	%VF.Measure{
    173 		has_unit: build(:unit),
    174 		has_numerical_value: decimald(),
    175 	}
    176 end
    177 
    178 def build(:spatial_thing) do
    179 	%VF.SpatialThing{
    180 		name: str("some name"),
    181 		mappable_address: str("some mappable_address"),
    182 		lat: decimald(),
    183 		long: decimald(),
    184 		alt: decimald(),
    185 		note: str("some note"),
    186 	}
    187 end
    188 
    189 def build(:action_id) do
    190 	Enum.random(VF.Action.ID.values())
    191 end
    192 
    193 def build(:process_specification) do
    194 	%VF.ProcessSpecification{
    195 		name: str("some name"),
    196 		note: str("some note"),
    197 	}
    198 end
    199 
    200 def build(:resource_specification) do
    201 	%VF.ResourceSpecification{
    202 		name: str("some name"),
    203 		resource_classified_as: str_list("some uri"),
    204 		note: str("some note"),
    205 		images: file_list(),
    206 		default_unit_of_effort: build(:unit),
    207 		default_unit_of_resource: build(:unit),
    208 	}
    209 end
    210 
    211 def build(:recipe_resource) do
    212 	%VF.RecipeResource{
    213 		name: str("some name"),
    214 		unit_of_resource: build(:unit),
    215 		unit_of_effort: build(:unit),
    216 		resource_classified_as: str_list("some uri"),
    217 		resource_conforms_to: build(:resource_specification),
    218 		substitutable: bool(),
    219 		note: str("some note"),
    220 		images: file_list(),
    221 	}
    222 end
    223 
    224 def build(:recipe_process) do
    225 	dur = build(:iduration)
    226 	%VF.RecipeProcess{
    227 		name: str("some name"),
    228 		note: str("some note"),
    229 		process_classified_as: str_list("some uri"),
    230 		process_conforms_to: build(:process_specification),
    231 		has_duration_unit_type: dur.unit_type,
    232 		has_duration_numeric_duration: dur.numeric_duration,
    233 	}
    234 end
    235 
    236 def build(:recipe_exchange) do
    237 	%VF.RecipeExchange{
    238 		name: str("some name"),
    239 		note: str("some note"),
    240 	}
    241 end
    242 
    243 def build(:recipe_flow) do
    244 	resqty = build(:imeasure)
    245 	effqty = build(:imeasure)
    246 	%VF.RecipeFlow{
    247 		action_id: build(:action_id),
    248 		recipe_input_of: build(:recipe_process),
    249 		recipe_output_of: build(:recipe_process),
    250 		recipe_flow_resource: build(:recipe_resource),
    251 		resource_quantity_has_unit: resqty.has_unit,
    252 		resource_quantity_has_numerical_value: resqty.has_numerical_value,
    253 		effort_quantity_has_unit: effqty.has_unit,
    254 		effort_quantity_has_numerical_value: effqty.has_numerical_value,
    255 		recipe_clause_of: build(:recipe_exchange),
    256 		note: str("some note"),
    257 	}
    258 end
    259 
    260 def build(:person) do
    261 	%VF.Person{
    262 		type: :per,
    263 		name: str("some name"),
    264 		images: file_list(),
    265 		note: str("some note"),
    266 		primary_location: build(:spatial_thing),
    267 		user: str("some user"),
    268 		email: "#{str("user")}@example.com",
    269 		# Normally, these are encoded by zenroom (with whatever
    270 		# encodings it chooses to use), but for testing, this'll
    271 		# work alright.
    272 		ecdh_public_key: Base.encode64("some ecdh_public_key"),
    273 		eddsa_public_key: Base.encode64("some eddsa_public_key"),
    274 		ethereum_address: Base.encode64("some ethereum_address"),
    275 		reflow_public_key: Base.encode64("some reflow_public_key"),
    276 		bitcoin_public_key: Base.encode64("some bitcoin_public_key"),
    277 	}
    278 end
    279 
    280 def build(:organization) do
    281 	%VF.Organization{
    282 		type: :org,
    283 		name: str("some name"),
    284 		images: file_list(),
    285 		classified_as: str_list("some uri"),
    286 		note: str("some note"),
    287 		primary_location: build(:spatial_thing),
    288 	}
    289 end
    290 
    291 def build(:agent) do
    292 	type = if(bool(), do: :person, else: :person)
    293 	struct(VF.Agent, build_map!(type))
    294 end
    295 
    296 def build(:role_behavior) do
    297 	%VF.RoleBehavior{
    298 		name: str("some name"),
    299 		note: str("some note"),
    300 	}
    301 end
    302 
    303 def build(:agent_relationship_role) do
    304 	%VF.AgentRelationshipRole{
    305 		role_behavior: build(:role_behavior),
    306 		role_label: str("some role label"),
    307 		inverse_role_label: str("some role label"),
    308 		note: str("some note"),
    309 	}
    310 end
    311 
    312 def build(:agent_relationship) do
    313 	%VF.AgentRelationship{
    314 		subject: build(:agent),
    315 		object: build(:agent),
    316 		relationship: build(:agent_relationship_role),
    317 		# in_scope_of:
    318 		note: str("some note"),
    319 	}
    320 end
    321 
    322 def build(:agreement) do
    323 	%VF.Agreement{
    324 		name: str("some name"),
    325 		note: str("some note"),
    326 	}
    327 end
    328 
    329 def build(:scenario_definition) do
    330 	dur = build(:iduration)
    331 	%VF.ScenarioDefinition{
    332 		name: str("some name"),
    333 		note: str("some note"),
    334 		has_duration_unit_type: dur.unit_type,
    335 		has_duration_numeric_duration: dur.numeric_duration,
    336 	}
    337 end
    338 
    339 def build(:scenario) do
    340 	recurse? = bool()
    341 
    342 	%VF.Scenario{
    343 		name: str("some name"),
    344 		note: str("some note"),
    345 		has_beginning: now(),
    346 		has_end: now(),
    347 		defined_as: build(:scenario_definition),
    348 		refinement_of: if(recurse?, do: build(:scenario)),
    349 	}
    350 end
    351 
    352 def build(:plan) do
    353 	%VF.Plan{
    354 		name: str("some name"),
    355 		due: now(),
    356 		note: str("some note"),
    357 		refinement_of: build(:scenario),
    358 	}
    359 end
    360 
    361 def build(:process) do
    362 	%VF.Process{
    363 		name: str("some name"),
    364 		note: str("some note"),
    365 		has_beginning: now(),
    366 		has_end: now(),
    367 		finished: bool(),
    368 		classified_as: str_list("some uri"),
    369 		based_on: build(:process_specification),
    370 		# in_scope_of:
    371 		planned_within: build(:plan),
    372 		nested_in: build(:scenario),
    373 	}
    374 end
    375 
    376 def build(:product_batch) do
    377 	%VF.ProductBatch{
    378 		batch_number: str("some batch number"),
    379 		expiry_date: now(),
    380 		production_date: now(),
    381 	}
    382 end
    383 
    384 def build(:appreciation) do
    385 	%VF.Appreciation{
    386 		appreciation_of: build(:economic_event),
    387 		appreciation_with: build(:economic_event),
    388 		note: str("some note"),
    389 	}
    390 end
    391 
    392 def build(:intent) do
    393 	agent_mutex? = bool()
    394 	resqty = build(:imeasure)
    395 	effqty = build(:imeasure)
    396 	availqty = build(:imeasure)
    397 
    398 	%VF.Intent{
    399 		name: str("some name"),
    400 		action_id: build(:action_id),
    401 		provider: if(agent_mutex?, do: build(:agent)),
    402 		receiver: unless(agent_mutex?, do: build(:agent)),
    403 		input_of: build(:process),
    404 		output_of: build(:process),
    405 		resource_classified_as: str_list("some uri"),
    406 		resource_conforms_to: build(:resource_specification),
    407 		resource_inventoried_as_id: insert_economic_resource!().id,
    408 		resource_quantity_has_unit: resqty.has_unit,
    409 		resource_quantity_has_numerical_value: resqty.has_numerical_value,
    410 		effort_quantity_has_unit: effqty.has_unit,
    411 		effort_quantity_has_numerical_value: effqty.has_numerical_value,
    412 		available_quantity_has_unit: availqty.has_unit,
    413 		available_quantity_has_numerical_value: availqty.has_numerical_value,
    414 		at_location: build(:spatial_thing),
    415 		has_beginning: now(),
    416 		has_end: now(),
    417 		has_point_in_time: now(),
    418 		due: now(),
    419 		finished: bool(),
    420 		images: file_list(),
    421 		note: str("some note"),
    422 		# in_scope_of:
    423 		agreed_in: str("some uri"),
    424 	}
    425 end
    426 
    427 def build(:commitment) do
    428 	datetime_mutex? = bool()
    429 	resource_mutex? = bool()
    430 	resqty = build(:imeasure)
    431 	effqty = build(:imeasure)
    432 
    433 	%VF.Commitment{
    434 		action_id: build(:action_id),
    435 		provider: build(:agent),
    436 		receiver: build(:agent),
    437 		input_of: build(:process),
    438 		output_of: build(:process),
    439 		resource_classified_as: str_list("some uri"),
    440 		resource_conforms_to: if(resource_mutex?, do: build(:resource_specification)),
    441 		resource_inventoried_as_id: unless(resource_mutex?, do: insert_economic_resource!().id),
    442 		resource_quantity_has_unit: resqty.has_unit,
    443 		resource_quantity_has_numerical_value: resqty.has_numerical_value,
    444 		effort_quantity_has_unit: effqty.has_unit,
    445 		effort_quantity_has_numerical_value: effqty.has_numerical_value,
    446 		has_beginning: if(datetime_mutex?, do: now()),
    447 		has_end: if(datetime_mutex?, do: now()),
    448 		has_point_in_time: unless(datetime_mutex?, do: now()),
    449 		due: now(),
    450 		finished: bool(),
    451 		note: str("some note"),
    452 		# in_scope_of:
    453 		agreed_in: str("some uri"),
    454 		independent_demand_of: build(:plan),
    455 		at_location: build(:spatial_thing),
    456 		clause_of: build(:agreement),
    457 	}
    458 end
    459 
    460 def build(:fulfillment) do
    461 	resqty = build(:imeasure)
    462 	effqty = build(:imeasure)
    463 
    464 	%VF.Fulfillment{
    465 		note: str("some note"),
    466 		fulfilled_by: build(:economic_event),
    467 		fulfills: build(:commitment),
    468 		resource_quantity_has_unit: resqty.has_unit,
    469 		resource_quantity_has_numerical_value: resqty.has_numerical_value,
    470 		effort_quantity_has_unit: effqty.has_unit,
    471 		effort_quantity_has_numerical_value: effqty.has_numerical_value,
    472 	}
    473 end
    474 
    475 def build(:satisfaction) do
    476 	resqty = build(:imeasure)
    477 	effqty = build(:imeasure)
    478 	mutex? = bool()
    479 
    480 	%VF.Satisfaction{
    481 		satisfied_by_commitment: if(mutex?, do: build(:commitment)),
    482 		satisfied_by_event_id: unless(mutex?, do: insert_economic_event!().id),
    483 		satisfies: build(:intent),
    484 		resource_quantity_has_unit: resqty.has_unit,
    485 		resource_quantity_has_numerical_value: resqty.has_numerical_value,
    486 		effort_quantity_has_unit: effqty.has_unit,
    487 		effort_quantity_has_numerical_value: effqty.has_numerical_value,
    488 		note: str("some note"),
    489 	}
    490 end
    491 
    492 def build(:claim) do
    493 	resqty = build(:imeasure)
    494 	effqty = build(:imeasure)
    495 
    496 	%VF.Claim{
    497 		action_id: build(:action_id),
    498 		provider: build(:agent),
    499 		receiver: build(:agent),
    500 		resource_classified_as: str_list("some uri"),
    501 		resource_conforms_to: build(:resource_specification),
    502 		resource_quantity_has_unit: resqty.has_unit,
    503 		resource_quantity_has_numerical_value: resqty.has_numerical_value,
    504 		effort_quantity_has_unit: effqty.has_unit,
    505 		effort_quantity_has_numerical_value: effqty.has_numerical_value,
    506 		triggered_by: if(bool(), do: build(:economic_event), else: nil),
    507 		due: now(),
    508 		finished: bool(),
    509 		agreed_in: str("some uri"),
    510 		note: str("some note"),
    511 		# in_scope_of:
    512 	}
    513 end
    514 
    515 def build(:settlement) do
    516 	resqty = build(:imeasure)
    517 	effqty = build(:imeasure)
    518 
    519 	%VF.Settlement{
    520 		settled_by: build(:economic_event),
    521 		settles: build(:claim),
    522 		resource_quantity_has_unit: resqty.has_unit,
    523 		resource_quantity_has_numerical_value: resqty.has_numerical_value,
    524 		effort_quantity_has_unit: effqty.has_unit,
    525 		effort_quantity_has_numerical_value: effqty.has_numerical_value,
    526 		note: str("some note"),
    527 	}
    528 end
    529 
    530 def build(:proposal) do
    531 	%VF.Proposal{
    532 		name: str("some name"),
    533 		has_beginning: now(),
    534 		has_end: now(),
    535 		unit_based: bool(),
    536 		note: str("some note"),
    537 		eligible_location: build(:spatial_thing),
    538 	}
    539 end
    540 
    541 def build(:proposed_intent) do
    542 	%VF.ProposedIntent{
    543 		reciprocal: bool(),
    544 		publishes: build(:intent),
    545 		published_in: build(:proposal),
    546 	}
    547 end
    548 
    549 def build(:proposed_to) do
    550 	%VF.ProposedTo{
    551 		proposed_to: build(:agent),
    552 		proposed: build(:proposal),
    553 	}
    554 end
    555 
    556 def insert_economic_event!() do
    557 	agent = insert!(:agent)
    558 	Zenflows.VF.EconomicEvent.Domain.create!(%{
    559 		action_id: "raise",
    560 		provider_id: agent.id,
    561 		receiver_id: agent.id,
    562 		resource_classified_as: str_list("some uri"),
    563 		resource_conforms_to_id: insert!(:resource_specification).id,
    564 		resource_quantity: %{
    565 			has_numerical_value: decimald(),
    566 			has_unit_id: insert!(:unit).id,
    567 		},
    568 		has_point_in_time: now(),
    569 	}, %{name: str("some name")})
    570 end
    571 
    572 def insert_economic_resource!() do
    573 	%{resource_inventoried_as_id: id} = insert_economic_event!()
    574 	Zenflows.VF.EconomicResource.Domain.one!(id)
    575 end
    576 end