anchor.diff:  	 handle_cast/2,
   	 handle_info/2,
   	 terminate/2,
  -	 code_change/3
  +	 code_change/3,
  +
  +	 test/0
   	]).
   
   %%--------------------------------------------------------------------
  
positive.code: %% Records
  %%--------------------------------------------------------------------
  -record(state, {
  	  fn,
  	  file_mtime,
  	  last_fail,
  	  userlist,
  	  addresslist
  	 }).
  
  %%--------------------------------------------------------------------
  
negative.code: %%           Address = address record()
  %%           Reason  = string()
  %%--------------------------------------------------------------------
  parse_address([], A) when is_record(A, address), A#address.user == undefined ->
      E = io_lib:format("address record incomplete (no user, address ~p)", [A#address.address]),
      {error, E};
  parse_address([], A) when is_record(A, address), A#address.address == undefined ->
      E = io_lib:format("address record incomplete (no address, user ~p)", [A#address.user]),
      {error, lists:flatten(E)};
  parse_address([], A) when is_record(A, address) ->
      A;
  
  parse_address([{user, V} | T], A) when is_record(A, address), is_list(V) ->
      parse_address(T, A#address{user=V});
  parse_address([{address, V} | T], A) when is_record(A, address), is_list(V) ->
      case sipurl:parse(V) of
  	URL when is_record(URL, sipurl) ->
  	    parse_address(T, A#address{address=V, url=URL});
  	_ ->
  	    E = io_lib:format("unparsable URL in address record (user ~p) : ~p", [A#address.user, V]),
  	    {error, lists:flatten(E)}
      end;
  parse_address([H | _T], A) when is_record(A, address) ->
      E = io_lib:format("bad data in address record (user ~p) : ~p", [A#address.user, H]),
      {error, lists:flatten(E)}.
  
  
  %%--------------------------------------------------------------------
  %% Function: verify_consistency(Users, Addresses)
  %%           Users    = list() of user record()
  %%           Adresses = list() of address record()
  %% Descrip.: Log warnings if there are any users that has no addresses
  %%           or any addresses that have no users.
  %% Returns : ok
  %%--------------------------------------------------------------------
  verify_consistency(Users, Addresses) ->
      {ok, NoAddressUsers, NoUserAddresses} = verify_consistency2(Users, Addresses),
      case NoAddressUsers of
  	[] -> ok;
  	_ ->
  	    logger:log(normal, "sipuserdb_file: Warning: The following user(s) have no address(es) : ~p",
  		       [NoAddressUsers])
      end,
      case NoUserAddresses of
  	[] -> ok;
  	_ ->
  	    logger:log(normal, "sipuserdb_file: Warning: The following address(es) have no user(s) : ~p",
  		       [NoUserAddresses])
      end,
      ok.
  
  %% verify_consistency2/2 - part of verify_consistency/2
  %% Returns : {ok, NoAddressUsers, NoUserAddresses}
  verify_consistency2(Users, Addresses) ->	     
      NoAddressUsers = get_no_address_users(Users, Addresses, []),
      NoUserAddresses = get_no_user_addresses(Addresses, Users, []),
      {ok, NoAddressUsers, NoUserAddresses}.
  
  %% get_no_address_users/3 - part of verify_consistency2/2
  %% Returns : NoAddressUsers = list() of string() - users who have no addresses
  get_no_address_users([H | T], Addresses, Res) when is_record(H, user) ->
      case get_no_address_users2(H#user.name, Addresses) of
  	true ->
  	    %% there was at least one address for this user
  	    get_no_address_users(T, Addresses, Res);
  	false ->
  	    %% there was no addresses for this user
  	    get_no_address_users(T, Addresses, [H#user.name | Res])
      end;
  get_no_address_users([], _Addresses, Res) ->
      lists:reverse(Res).
  
  %% get_no_address_users2/2 - part of get_no_address_users/3
  get_no_address_users2(Username, [#address{user=Username} | _T]) ->
      true;
  get_no_address_users2(Username, [_H | T]) ->
      get_no_address_users2(Username, T);
  get_no_address_users2(_Username, []) ->
      false.
  
  %% get_no_user_addresses/3 - part of verify_consistency2/2
  %% Returns : NoUserAddresses = list() of string() - addresses who have no user
  get_no_user_addresses([H | T], Users, Res) when is_record(H, address) ->
      case get_no_user_addresses2(H#address.user, Users) of
  	true ->
  	    %% there was at least one user for this address
  	    get_no_user_addresses(T, Users, Res);
  	false ->
  	    %% there was no user for this addresses
  	    get_no_user_addresses(T, Users, [H#address.address | Res])
      end;
  get_no_user_addresses([], _Users, Res) ->
      lists:reverse(Res).
  
  %% get_no_user_addresses2/2 - part of get_no_user_addresses/3
  get_no_user_addresses2(Username, [#user{name=Username} | _T]) ->
      true;
  get_no_user_addresses2(Username, [_H | T]) ->
      get_no_user_addresses2(Username, T);
  get_no_user_addresses2(_Username, []) ->
      false.
  
  
  %%====================================================================
  %% Test functions
  %%====================================================================
  
  %%--------------------------------------------------------------------
  %% Function: test()
  %% Descrip.: autotest callback
  %% Returns : ok | throw()
  %%--------------------------------------------------------------------
  test() ->
  
      %% test parse_address/1
      %%--------------------------------------------------------------------
      ParseAddress_URL1 = sipurl:parse("sip:ft@example.org"),
  
      io:format("test: parse_address/2 - 1~n"),
      %% valid entry
      #address{user    = "test",
  	     address = "sip:ft@example.org",
  	     url     = ParseAddress_URL1} =
  	parse_address([{user, "test"}, {address, "sip:ft@example.org"}], #address{}),
  
      io:format("test: parse_address/2 - 2~n"),
      %% unparsable URL
      {error, "unparsable URL in address record " ++ _} =
  	parse_address([{address, "unsupported:ft@example.org"}], #address{}),
  
      io:format("test: parse_address/2 - 3~n"),
      %% no user
      {error, "address record incomplete (no user, " ++ _} =
  	parse_address([{address, "sip:ft@example.org"}], #address{}),
  
      io:format("test: parse_address/2 - 4~n"),
      %% no address
      {error, "address record incomplete (no address, " ++ _} =
  	parse_address([{user, "test"}], #address{}),
  
      io:format("test: parse_address/2 - 5~n"),
      %% non-list user
      {error, "bad data in address record " ++ _} = parse_address([{user, none}], #address{}),
  
      io:format("test: parse_address/2 - 6~n"),
      %% non-list address
      {error, "bad data in address record " ++ _} = parse_address([{address, none}], #address{}),
  
      io:format("test: parse_address/2 - 7~n"),
      %% unknown parameter
      {error, "bad data in address record " ++ _} = parse_address([{true, "x"}], #address{}),
  
  
      %% test parse_user/3
      %%--------------------------------------------------------------------
      io:format("test: parse_user/3 - 1~n"),
      %% minimalistic case
      {ok, #user{name="foo"}, []} = parse_user([{name, "foo"}], #user{}, []),
  
      io:format("test: parse_user/3 - 2~n"),
      %% all settings possible
      {ok, #user{name     = "foo",
  	       password = "secret",
  	       classes  = [none],
  	       forward  = undefined
  	      }, ["test"]} =
  	parse_user([{name, "foo"}, {password, "secret"}, {classes, [none]}, {addresses, ["test"]}], #user{}, []),
  
      io:format("test: parse_user/3 - 3~n"),
      %% test not-implemented forward
      {error, "forward not implemented in sipuserdb_file yet" ++ _} = parse_user([{forward, "foo"}], #user{}, []),
  
      io:format("test: parse_user/3 - 4~n"),
      %% non-list username
      {error, "bad data in user record" ++ _} = parse_user([{name, none}], #user{}, []),
  
      io:format("test: parse_user/3 - 5~n"),
      %% without username
      {error, "user record incomplete (no user, " ++ _} = parse_user([{password, "secret"}], #user{}, []),
  
  
      %% test parse_term/3
      %%--------------------------------------------------------------------
      ParseTermUserL1 = [#user{name = "t1"},
  		       #user{name = "t2"}],
      ParseTermAddrL1 = [#address{user = "t1", address = "sip:1@example.net", url = sipurl:parse("sip:1@example.net")},
  		       #address{user = "t2", address = "sip:2@example.net", url = sipurl:parse("sip:2@example.net")}],
  
      ParseTermAddrL2 = [#address{user = "t1", address = "sip:1.1@example.net", url = sipurl:parse("sip:1.1@example.net")},
  		       #address{user = "t1", address = "sip:1.2@example.net", url = sipurl:parse("sip:1.2@example.net")},
  		       #address{user = "t2", address = "sip:2.1@example.net", url = sipurl:parse("sip:2.1@example.net")},
  		       #address{user = "t2", address = "sip:2.2@example.net", url = sipurl:parse("sip:2.2@example.net")}],
  
      io:format("test: parse_term/3 - 1~n"),
      %% uncomplicated case
      {ok, ParseTermUserL1, ParseTermAddrL1} = parse_term([{user, [{name, "t1"}]},
  							 {address, [{user, "t1"},
  								    {address, "sip:1@example.net"}]},
  							 
  							 {user, [{name, "t2"}]},
  							 {address, [{user, "t2"},
  								    {address, "sip:2@example.net"}]}
  							 ], [], []),
  
      io:format("test: parse_term/3 - 2~n"),
      %% addresses grouped with user
      {ok, ParseTermUserL1, ParseTermAddrL2} = parse_term([{user, [{name, "t1"}, 
  								 {addresses, ["sip:1.1@example.net",
  									      "sip:1.2@example.net"]}]},
  							 {user, [{name, "t2"},
  								 {addresses, ["sip:2.1@example.net",
  									      "sip:2.2@example.net"]}]}
  							], [], []),
  
      io:format("test: parse_term/3 - 3~n"),
      %% unknown term
      {error, "sipuserdb_file: Unknown data :" ++ _} = parse_term([{none, []}], [], []),
  
      io:format("test: parse_term/3 - 4~n"),
      %% user without name
      {error, "bad data in user record" ++ _} = parse_term([{user, [{password, secret}]}], [], []),
  
      io:format("test: parse_term/3 - 5~n"),
      %% invalid address listed with user
      {error, "unparsable URL in address record " ++ _} =
  	parse_term([{user, [{name, "t1"},
  			    {addresses, ["unsupported:ft@example.org"]}
  			   ]}], [], []),
  
      io:format("test: parse_term/3 - 6~n"),
      %% invalid address
      {error, "unparsable URL in address record " ++ _} =
  	parse_term([{address, [{user, "t1"},
  			       {address, "unsupported:ft@example.org"}
  			      ]}], [], []),
  
  
      %% test verify_consistency2(Users, Addresses)
      %%--------------------------------------------------------------------
      io:format("test: verify_consistency2/2 - 1~n"),
      %% no warnings
      {ok, VC_U1, VC_A1} = parse_term([{user, [{name, "test1"},
  					     {addresses, ["sip:test@example.org"]}
  					    ]},
  				     {user, [{name, "test2"},
  					     {addresses, ["sip:test@example.org"]}
  					    ]}
  				     ], [], []),
      {ok, [], []} = verify_consistency2(VC_U1, VC_A1),
  
      io:format("test: verify_consistency2/2 - 2~n"),
      %% one address warning
      {ok, VC_U2, VC_A2} = parse_term([{user, [{name, "test1"},
  					     {addresses, ["sip:test@example.org"]}
  					    ]},
  				     {user, [{name, "test2"},
  					     {addresses, ["sip:test@example.org"]}
  					    ]},
  				     {address, [{user, "x"},
  						{address, "sip:x@example.org"}]}
  				     ], [], []),
      {ok, [], ["sip:x@example.org"]} = verify_consistency2(VC_U2, VC_A2),
  
      io:format("test: verify_consistency2/2 - 3~n"),
      %% one user warning
      {ok, VC_U3, VC_A3} = parse_term([{user, [{name, "test1"}]},
  				     {user, [{name, "test2"},
  					     {addresses, ["sip:test@example.org"]}
  					    ]}
  				     ], [], []),
      {ok, ["test1"], []} = verify_consistency2(VC_U3, VC_A3),
  
  
      
      ok.
  