<commit_before>-module(js_links_machine).

-compile(export_all).

-include_lib("eqc/include/eqc.hrl").
-include_lib("eqc/include/eqc_component.hrl").
-include_lib("eqc/include/eqc_dynamic_cluster.hrl").
-include_lib("jsongen.hrl").

%% Super fragile below
-record(eqc_statem_history,{state, args, features, result}).

%%-define(debug,true).

-ifdef(debug).
-define(LOG(X,Y),
	io:format("{~p,~p}: ~s~n", [?MODULE,?LINE,io_lib:format(X,Y)])).
-else.
-define(LOG(X,Y),true).
-endif.


api_spec() ->
  #api_spec{}.

initial_state() ->
  PrivateState = 
    case exists_private_function(initial_state,0) of
      true ->
	(private_module()):initial_state();
      false ->
	void
    end,
  #state
    {static_links=initial_links(),
     initialized=false,
     dynamic_links=jsl_dynamic_links:initialize(20),
     private_state=PrivateState}.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

start_pre(State) ->
  not(State#state.initialized).

start_args(_State) ->
  [].

start() ->
  %%jsg_store:open_clean_db(),
  jsg_utils:clear_schema_cache(),
  true = ets:match_delete(jsg_store,{{object,'_'},'_'}),
  true = ets:match_delete(jsg_store,{{term,'_'},'_'}),
  true = ets:match_delete(jsg_store,{{link,'_'},'_'}),
  true = ets:match_delete(jsg_store,{{reverse_link,'_'},'_'}),
  httpc:reset_cookies().

start_next(State,_,_) ->
  State#state{initialized=true}.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

link_pre(State) ->
  State#state.initialized.

link_args(State) ->
  case jsl_dynamic_links:is_empty(State#state.dynamic_links) of
    true ->
      ?LET(FinalLink,
	   eqc_gen:oneof(State#state.static_links),
	   gen_call(FinalLink));
    false ->
      ?LET
	 (FinalLink,
	  eqc_gen:
	     frequency
	       ([{7,
		  ?LET
		    (Title,
		     eqc_gen:oneof
		       (jsl_dynamic_links:titles
			  (State#state.dynamic_links)),
		     eqc_gen:oneof
		       (jsl_dynamic_links:links
			  (Title,State#state.dynamic_links)))},
		{1,
		 eqc_gen:oneof(State#state.static_links)}]),
	   gen_call(FinalLink))
      end.

link_pre(State,[Link,_]) ->
  (State#state.initialized==true) 
    andalso ((jsg_links:link_type(Link)==static)
	     orelse 
	     jsl_dynamic_links:is_element(Link,State#state.dynamic_links))
    andalso link_permitted(State,Link).

link_permitted(State,Link) ->
  make_call(link_permitted,fun link_permitted_int/2,[State,Link]).

link_permitted_int(_State,_Link) ->
  true.

gen_call(Link) ->
  ?LET(Parms,
       gen_http_request(Link),
       [Link,Parms]).

link_post(_State,Args,{'EXIT',Error}) ->
  io:format
    ("~n*** Error: when calling generating/issuing the http call~n~s~n"++
       "Erlang raised the exception~n~p~n",
     [format_http_call(Args),Error]),
  error(bad_link);
link_post(State,Args,Result) ->
  try make_call(postcondition,fun postcondition_int/3,[State,Args,Result])
  catch Class:Reason ->
      io:format
	("Warning: postcondition/3 raises exception ~p~n",
	 [Reason]),
      StackTrace = erlang:get_stacktrace(),
      erlang:raise(Class,Reason,StackTrace)
  end.

postcondition_int(_State,Args,Result) ->
  case validate_call_not_error_result(Args,Result) of
    true ->
      case http_result_code(Result) of
	200 ->
	  validate_call_result_body(Args,Result);
	Other ->
	  io:format
	    ("~n*** Error: postcondition error: for http call~n~s~nhttp responded with result code ~p, expected result code 200~n",
	     [format_http_call(Args),Other]),
	  false
      end;
    _ ->
      io:format("validation failed~n"),
      false
  end.

validate_call_not_error_result(Args,Result) ->
  case http_result_type(Result) of
    ok ->
      true;
    {error,_Error} ->
      io:format
	("~n*** Error: postcondition error: for http call~n~s~nhttp responded with error ~p~n",
	 [format_http_call(Args),http_error(Result)]),
      false
  end.

validate_call_result_body(Args,Result) ->
  Link = jsg_links:link_def(args_link(Args)),
  Schema = jsg_links:link_schema(args_link(Args)),
  case jsg_jsonschema:propertyValue(Link,"targetSchema") of
    undefined ->
      true;
    TargetSchema ->
      RealTargetSchema = jsg_links:get_schema(TargetSchema,Schema),
      case response_has_json_body(Result) of
	false ->
	  false;
	true ->
	  Body = http_body(Result),
	  Validator = get_option(validator),
	  try Validator:validate(RealTargetSchema,Body)
	  catch _Class:Reason ->
	      io:format
		("~n*** Error: postcondition error: for http call~n~s~n"++
		   "the JSON value~n~s~n"++
		   "did not validate against the schema~n~s~n"++
		   "due to error~n~p~n",
		 [format_http_call(Args),
		  Body,
		  mochijson2:encode(RealTargetSchema),
		  Reason]),
	      io:format
		("Stacktrace:~n~p~n",
		 [erlang:get_stacktrace()]),
	      false
	  end
      end
  end.

link_next(State,Result,Args) ->
  try make_call(next_state,fun next_state_int/3,[State,Result,Args])
  catch Class:Reason ->
      io:format
	("Warning: next_state/3 raises exception ~p~n",
	 [Reason]),
      StackTrace = erlang:get_stacktrace(),
      erlang:raise(Class,Reason,StackTrace)
  end.

next_state_int(State,Result,[Link,_]) ->
  case Result of
    {ok,{{_,_Code,_},_Headers,_Body}} ->
      LinksToAdd =
	case response_has_body(Result) of
	  true ->
	    JSONbody = mochijson2:decode(http_body(Result)),
	    jsg_links:extract_dynamic_links
	      (Link,JSONbody,jsg_links:intern_object(JSONbody));
	  _ ->
	    []
	end,
      State#state
	{
	dynamic_links=<commit_msg>status code checking added<commit_after>-module(js_links_machine).

-compile(export_all).

-include_lib("eqc/include/eqc.hrl").
-include_lib("eqc/include/eqc_component.hrl").
-include_lib("eqc/include/eqc_dynamic_cluster.hrl").
-include_lib("jsongen.hrl").

%% Super fragile below
-record(eqc_statem_history,{state, args, features, result}).

%%-define(debug,true).

-ifdef(debug).
-define(LOG(X,Y),
	io:format("{~p,~p}: ~s~n", [?MODULE,?LINE,io_lib:format(X,Y)])).
-else.
-define(LOG(X,Y),true).
-endif.


api_spec() ->
  #api_spec{}.

initial_state() ->
  PrivateState = 
    case exists_private_function(initial_state,0) of
      true ->
	(private_module()):initial_state();
      false ->
	void
    end,
  #state
    {static_links=initial_links(),
     initialized=false,
     dynamic_links=jsl_dynamic_links:initialize(20),
     private_state=PrivateState}.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

start_pre(State) ->
  not(State#state.initialized).

start_args(_State) ->
  [].

start() ->
  %%jsg_store:open_clean_db(),
  jsg_utils:clear_schema_cache(),
  true = ets:match_delete(jsg_store,{{object,'_'},'_'}),
  true = ets:match_delete(jsg_store,{{term,'_'},'_'}),
  true = ets:match_delete(jsg_store,{{link,'_'},'_'}),
  true = ets:match_delete(jsg_store,{{reverse_link,'_'},'_'}),
  httpc:reset_cookies().

start_next(State,_,_) ->
  State#state{initialized=true}.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

link_pre(State) ->
  State#state.initialized.

link_args(State) ->
  case jsl_dynamic_links:is_empty(State#state.dynamic_links) of
    true ->
      ?LET(FinalLink,
	   eqc_gen:oneof(State#state.static_links),
	   gen_call(FinalLink));
    false ->
      ?LET
	 (FinalLink,
	  eqc_gen:
	     frequency
	       ([{7,
		  ?LET
		    (Title,
		     eqc_gen:oneof
		       (jsl_dynamic_links:titles
			  (State#state.dynamic_links)),
		     eqc_gen:oneof
		       (jsl_dynamic_links:links
			  (Title,State#state.dynamic_links)))},
		{1,
		 eqc_gen:oneof(State#state.static_links)}]),
	   gen_call(FinalLink))
      end.

link_pre(State,[Link,_]) ->
  (State#state.initialized==true) 
    andalso ((jsg_links:link_type(Link)==static)
	     orelse 
	     jsl_dynamic_links:is_element(Link,State#state.dynamic_links))
    andalso link_permitted(State,Link).

link_permitted(State,Link) ->
  make_call(link_permitted,fun link_permitted_int/2,[State,Link]).

link_permitted_int(_State,_Link) ->
  true.

gen_call(Link) ->
  ?LET(Parms,
       gen_http_request(Link),
       [Link,Parms]).

link_post(_State,Args,{'EXIT',Error}) ->
  io:format
    ("~n*** Error: when calling generating/issuing the http call~n~s~n"++
       "Erlang raised the exception~n~p~n",
     [format_http_call(Args),Error]),
  error(bad_link);
link_post(State,Args,Result) ->
  try make_call(postcondition,fun postcondition_int/3,[State,Args,Result])
  catch Class:Reason ->
      io:format
	("Warning: postcondition/3 raises exception ~p~n",
	 [Reason]),
      StackTrace = erlang:get_stacktrace(),
      erlang:raise(Class,Reason,StackTrace)
  end.

postcondition_int(_State,Args,Result) ->
  case validate_call_not_error_result(Args,Result) of
    true ->
      Link = jsg_links:link_def(args_link(Args)),
      Schema = jsg_links:link_schema(args_link(Args)),
      validate_call_result_body(Args, Result, Link, Schema) and
          validate_response_code(Args, Result, Link, Schema);
    _ ->
      io:format("validation failed~n"),
      false
  end.

validate_call_not_error_result(Args,Result) ->
  case http_result_type(Result) of
    ok ->
      true;
    {error,_Error} ->
      io:format
	("~n*** Error: postcondition error: for http call~n~s~nhttp responded with error ~p~n",
	 [format_http_call(Args),http_error(Result)]),
      false
  end.

validate_call_result_body(Args,Result,Link,Schema) ->
  case jsg_jsonschema:propertyValue(Link,"targetSchema") of
    undefined ->
      true;
    TargetSchema ->
      RealTargetSchema = jsg_links:get_schema(TargetSchema,Schema),
      case response_has_json_body(Result) of
	false ->
	  false;
	true ->
	  Body = http_body(Result),
	  Validator = get_option(validator),
	  try Validator:validate(RealTargetSchema,Body)
	  catch _Class:Reason ->
	      io:format
		("~n*** Error: postcondition error: for http call~n~s~n"++
		   "the JSON value~n~s~n"++
		   "did not validate against the schema~n~s~n"++
		   "due to error~n~p~n",
		 [format_http_call(Args),
		  Body,
		  mochijson2:encode(RealTargetSchema),
		  Reason]),
	      io:format
		("Stacktrace:~n~p~n",
		 [erlang:get_stacktrace()]),
	      false
	  end
      end
  end.

validate_response_code(Args, Result, Link, Schema) ->
  case jsg_jsonschema:propertyValue(Link, "targetSchema") of
    undefined -> true;
    TargetSchema ->
      RealTargetSchema = jsg_links:get_schema(TargetSchema, Schema),
      SchemaStatusCode = get_status_code(RealTargetSchema),
        case
          case SchemaStatusCode of
            undefined ->
              http_result_code(Result) == 200;
            SchemaStatusCode when is_list(SchemaStatusCode) ->
              lists:member(http_result_code(Result), SchemaStatusCode);
            SchemaStatusCode ->
              SchemaStatusCode == http_result_code(Result)
            end
          of
            false ->
              io:format
                ("~n*** Error: postcondition error: for http call~n~s~n"++
                    "the HTTP response code: ~p~n"++
                    "expected: ~p~n",
                 [format_http_call(Args), SchemaStatusCode, http_result_code(Result)]),
              false;
           true -> true
        end
  end.

get_status_code(Schema={struct, ListOfValues}) ->
  case ListOfValues of
    [{<<"oneOf">>, JsonSchemaList}] ->
      lists:map(fun(X) -> get_status_code(X) end, JsonSchemaList);
     _ ->
      jsg_jsonschema:propertyValue(Schema, "status")
  end.

link_next(State,Result,Args) ->
  try make_call(next_state,fun next_state_int/3,[State,Result,Args])
  catch Class:Reason ->
      io:format
	("Warning: next_state/3 raises exception ~p~n",
	 [Reason]),
      StackTrace = erlang:get_stacktrace(),
      erlang:raise(Class,Reason,StackTrace)
  end.

next_state_int(State,Result,[Link,_]) ->
  case Result of
    {ok,{{_,_Code,_},_Headers,_Body}} ->
      LinksToAdd =
	case response_has_body(Result) of
	  true ->
	    JSONbody = mochijson2:decode(http_body(Result)),
	    jsg_links:extract_dynamic_links
	      (Link,JSONbody,jsg_links:intern_object(JSONbody));
	  _ ->
	    []
	end,
      State#state
	{
	dynamic_links=
{'fraction_non_alphanumeric': 0.16741442339647725, 'fraction_numerical': 0.0027417746759720836, 'mean_word_length': 3.901058631921824, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 93, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3067545', 'n_tokens_mistral': 4350, 'n_tokens_neox': 4167, 'n_words': 622}