rhosync 2.0.0.beta3 → 2.0.0.beta4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/doc/protocol.html +87 -87
- data/lib/rhosync/jobs/bulk_data_job.rb +51 -2
- data/lib/rhosync/server.rb +15 -13
- data/lib/rhosync/source.rb +3 -0
- data/lib/rhosync/test_methods.rb +78 -0
- data/lib/rhosync/version.rb +1 -1
- data/lib/rhosync.rb +4 -0
- data/spec/api/get_source_params_spec.rb +2 -1
- data/spec/api/list_sources_spec.rb +3 -3
- data/spec/apps/rhotestapp/settings/settings.yml +13 -0
- data/spec/apps/rhotestapp/sources/fixed_schema_adapter.rb +9 -0
- data/spec/doc/doc_spec.rb +0 -2
- data/spec/jobs/bulk_data_job_spec.rb +10 -6
- data/spec/server/server_spec.rb +32 -7
- data/spec/spec_helper.rb +51 -17
- metadata +5 -3
| @@ -48,6 +48,50 @@ module Rhosync | |
| 48 48 | 
             
                  end
         | 
| 49 49 | 
             
                  counter
         | 
| 50 50 | 
             
                end
         | 
| 51 | 
            +
                
         | 
| 52 | 
            +
                # Loads data into fixed schema table based on source settings
         | 
| 53 | 
            +
                def self.import_data_to_fixed_schema(db,source)
         | 
| 54 | 
            +
                  data = source.get_data(:md)
         | 
| 55 | 
            +
                  counter = {}
         | 
| 56 | 
            +
                  columns,qm = [],[]
         | 
| 57 | 
            +
                  create_table = ['object varchar']
         | 
| 58 | 
            +
                  schema = JSON.parse(source.schema)
         | 
| 59 | 
            +
                  
         | 
| 60 | 
            +
                  db.transaction do |database|
         | 
| 61 | 
            +
                    # Create a table with columns specified by 'property' array in settings
         | 
| 62 | 
            +
                    schema['property'].each do |column|
         | 
| 63 | 
            +
                      create_table << "#{column.keys[0]} varchar default NULL" 
         | 
| 64 | 
            +
                      columns << column.keys[0]
         | 
| 65 | 
            +
                      qm << '?'
         | 
| 66 | 
            +
                    end
         | 
| 67 | 
            +
                    database.execute("CREATE TABLE #{source.name}(
         | 
| 68 | 
            +
                      #{create_table.join(",")} );")
         | 
| 69 | 
            +
                    
         | 
| 70 | 
            +
                    # Insert each object as single row in fixed schema table
         | 
| 71 | 
            +
                    database.prepare("insert into #{source.name} 
         | 
| 72 | 
            +
                      (object,#{columns.join(',')}) values (?,#{qm.join(',')})") do |stmt|
         | 
| 73 | 
            +
                      data.each do |obj,row|
         | 
| 74 | 
            +
                        args = [obj]
         | 
| 75 | 
            +
                        columns.each do |col|
         | 
| 76 | 
            +
                          args << row[col]
         | 
| 77 | 
            +
                        end  
         | 
| 78 | 
            +
                        stmt.execute(args)
         | 
| 79 | 
            +
                      end
         | 
| 80 | 
            +
                    end
         | 
| 81 | 
            +
                    
         | 
| 82 | 
            +
                    # Create indexes for specified columns in settings 'index'
         | 
| 83 | 
            +
                    schema['index'].each do |index|
         | 
| 84 | 
            +
                      database.execute("CREATE INDEX #{index.keys[0]} on #{source.name} (#{index.values[0]});")
         | 
| 85 | 
            +
                    end if schema['index']
         | 
| 86 | 
            +
                    
         | 
| 87 | 
            +
                    # Create unique indexes for specified columns in settings 'unique_index'
         | 
| 88 | 
            +
                    schema['unique_index'].each do |index|
         | 
| 89 | 
            +
                      database.execute("CREATE UNIQUE INDEX #{index.keys[0]} on #{source.name} (#{index.values[0]});")
         | 
| 90 | 
            +
                    end if schema['unique_index']
         | 
| 91 | 
            +
                  end
         | 
| 92 | 
            +
                
         | 
| 93 | 
            +
                  return {}
         | 
| 94 | 
            +
                end
         | 
| 51 95 |  | 
| 52 96 | 
             
                def self.refs_to_s(refs)
         | 
| 53 97 | 
             
                  str = ''
         | 
| @@ -84,7 +128,12 @@ module Rhosync | |
| 84 128 | 
             
                      :user_id => bulk_data.user_id})
         | 
| 85 129 | 
             
                    source.source_id = src_counter
         | 
| 86 130 | 
             
                    src_counter += 1
         | 
| 87 | 
            -
                    source_attrib_refs =  | 
| 131 | 
            +
                    source_attrib_refs = nil
         | 
| 132 | 
            +
                    if source.schema
         | 
| 133 | 
            +
                      source_attrib_refs = import_data_to_fixed_schema(db,source)
         | 
| 134 | 
            +
                    else
         | 
| 135 | 
            +
                      source_attrib_refs = import_data_to_object_values(db,source)
         | 
| 136 | 
            +
                    end
         | 
| 88 137 | 
             
                    sources_refs[source_name] = 
         | 
| 89 138 | 
             
                      {:source => source, :refs => source_attrib_refs}
         | 
| 90 139 | 
             
                    lap_timer("finished importing sqlite data for #{source_name}",timer)
         | 
| @@ -99,7 +148,7 @@ module Rhosync | |
| 99 148 | 
             
                  hsql_file = dbfile + ".hsqldb"
         | 
| 100 149 | 
             
                  raise Exception.new("Error running hsqldata") unless 
         | 
| 101 150 | 
             
                    system('java','-cp', File.join(File.dirname(__FILE__),'..','..','..','vendor','hsqldata.jar'),
         | 
| 102 | 
            -
                    'com.rhomobile.hsqldata.HsqlData', dbfile, hsql_file | 
| 151 | 
            +
                    'com.rhomobile.hsqldata.HsqlData', dbfile, hsql_file)
         | 
| 103 152 | 
             
                end
         | 
| 104 153 |  | 
| 105 154 | 
             
                def self.get_file_args(bulk_data_name,ts)
         | 
    
        data/lib/rhosync/server.rb
    CHANGED
    
    | @@ -112,10 +112,6 @@ module Rhosync | |
| 112 112 | 
             
                        params[:source_name] ? {:source_name => current_source.name} : {:source_name => '*'}) 
         | 
| 113 113 | 
             
                    end  
         | 
| 114 114 | 
             
                  end
         | 
| 115 | 
            -
                  
         | 
| 116 | 
            -
                  def source_config
         | 
| 117 | 
            -
                    { "sources" => Rhosync.get_config(Rhosync.base_directory)[:sources] }
         | 
| 118 | 
            -
                  end
         | 
| 119 115 |  | 
| 120 116 | 
             
                  def catch_all
         | 
| 121 117 | 
             
                    begin
         | 
| @@ -136,15 +132,21 @@ module Rhosync | |
| 136 132 | 
             
                Rhosync.log "Rhosync Server v#{Rhosync::VERSION} started..."
         | 
| 137 133 |  | 
| 138 134 | 
             
                before do
         | 
| 139 | 
            -
                   | 
| 140 | 
            -
                     | 
| 141 | 
            -
             | 
| 142 | 
            -
             | 
| 143 | 
            -
             | 
| 144 | 
            -
             | 
| 145 | 
            -
                     | 
| 146 | 
            -
             | 
| 147 | 
            -
             | 
| 135 | 
            +
                  begin
         | 
| 136 | 
            +
                    if params["cud"]
         | 
| 137 | 
            +
                      cud = JSON.parse(params["cud"])
         | 
| 138 | 
            +
                      params.delete("cud")
         | 
| 139 | 
            +
                      params.merge!(cud)
         | 
| 140 | 
            +
                    end
         | 
| 141 | 
            +
                    if request.env['CONTENT_TYPE'] == 'application/json'
         | 
| 142 | 
            +
                      params.merge!(JSON.parse(request.body.read))
         | 
| 143 | 
            +
                      request.body.rewind
         | 
| 144 | 
            +
                    end      
         | 
| 145 | 
            +
                  rescue JSON::ParserError => jpe
         | 
| 146 | 
            +
                    throw :halt, [500, "Server error while processing client data"]
         | 
| 147 | 
            +
                  rescue Exception => e
         | 
| 148 | 
            +
                    throw :halt, [500, "Internal server error"]
         | 
| 149 | 
            +
                  end
         | 
| 148 150 | 
             
                  if params[:version] and params[:version].to_i < 3
         | 
| 149 151 | 
             
                    throw :halt, [404, "Server supports version 3 or higher of the protocol."]
         | 
| 150 152 | 
             
                  end
         | 
    
        data/lib/rhosync/source.rb
    CHANGED
    
    | @@ -13,6 +13,7 @@ module Rhosync | |
| 13 13 | 
             
                field :queue,:string
         | 
| 14 14 | 
             
                field :query_queue,:string
         | 
| 15 15 | 
             
                field :cud_queue,:string
         | 
| 16 | 
            +
                field :schema, :string
         | 
| 16 17 | 
             
                attr_accessor :app_id, :user_id
         | 
| 17 18 | 
             
                validates_presence_of :name #, :source_id
         | 
| 18 19 |  | 
| @@ -27,6 +28,7 @@ module Rhosync | |
| 27 28 | 
             
                  fields[:partition_type] ||= :user
         | 
| 28 29 | 
             
                  fields[:poll_interval] ||= 300
         | 
| 29 30 | 
             
                  fields[:sync_type] ||= :incremental
         | 
| 31 | 
            +
                  fields[:schema] = fields[:schema].to_json if fields[:schema]
         | 
| 30 32 | 
             
                end
         | 
| 31 33 |  | 
| 32 34 | 
             
                def self.create(fields,params)
         | 
| @@ -43,6 +45,7 @@ module Rhosync | |
| 43 45 | 
             
                end
         | 
| 44 46 |  | 
| 45 47 | 
             
                def update(fields)
         | 
| 48 | 
            +
                  fields = fields.with_indifferent_access # so we can access hash keys as symbols
         | 
| 46 49 | 
             
                  self.class.set_defaults(fields)
         | 
| 47 50 | 
             
                  super(fields)
         | 
| 48 51 | 
             
                end
         | 
    
        data/lib/rhosync/test_methods.rb
    CHANGED
    
    | @@ -4,6 +4,8 @@ module Rhosync | |
| 4 4 | 
             
              end
         | 
| 5 5 |  | 
| 6 6 | 
             
              module TestMethods
         | 
| 7 | 
            +
                # Initializes the source adapter under test for a given user, typically in a before(:each) block
         | 
| 8 | 
            +
                # setup_test_for(Product,'testuser') #=> 'testuser' will be used by rest of the specs
         | 
| 7 9 | 
             
                def setup_test_for(adapter,user_id)
         | 
| 8 10 | 
             
                  app_id = 'application'
         | 
| 9 11 | 
             
                  s_fields = {
         | 
| @@ -23,15 +25,51 @@ module Rhosync | |
| 23 25 | 
             
                  @ss = SourceSync.new(@s)
         | 
| 24 26 | 
             
                end
         | 
| 25 27 |  | 
| 28 | 
            +
                # Executes the adapter's query method and returns 
         | 
| 29 | 
            +
                # the master document (:md) stored in redis
         | 
| 30 | 
            +
                # For example, if your source adapter query method was:
         | 
| 31 | 
            +
                # def query(params=nil)
         | 
| 32 | 
            +
                #   @result = { 
         | 
| 33 | 
            +
                #     "1"=>{"name"=>"Acme", "industry"=>"Electronics"},
         | 
| 34 | 
            +
                #     "2"=>{"name"=>"Best", "industry"=>"Software"}
         | 
| 35 | 
            +
                #   }
         | 
| 36 | 
            +
                # end
         | 
| 37 | 
            +
                # 
         | 
| 38 | 
            +
                # test_query would return:
         | 
| 39 | 
            +
                # { 
         | 
| 40 | 
            +
                #   "1"=>{"name"=>"Acme", "industry"=>"Electronics"},
         | 
| 41 | 
            +
                #   "2"=>{"name"=>"Best", "industry"=>"Software"}
         | 
| 42 | 
            +
                # }
         | 
| 26 43 | 
             
                def test_query
         | 
| 27 44 | 
             
                  @ss.process_query
         | 
| 28 45 | 
             
                  return md
         | 
| 29 46 | 
             
                end
         | 
| 30 47 |  | 
| 48 | 
            +
                # Returns any errors stored in redis for the previous source adapter query
         | 
| 49 | 
            +
                # For example: {"query-error"=>{"message"=>"error connecting to web service!"}}
         | 
| 31 50 | 
             
                def query_errors
         | 
| 32 51 | 
             
                  @s.get_data(:errors)
         | 
| 33 52 | 
             
                end
         | 
| 34 53 |  | 
| 54 | 
            +
                # Execute's the adapter's create method with a provided record and
         | 
| 55 | 
            +
                # returns the object string from the create method.  If the create method
         | 
| 56 | 
            +
                # returns a string, then a link will be saved for the device next time
         | 
| 57 | 
            +
                # it synchronizes.  This link can be tested here.
         | 
| 58 | 
            +
                #
         | 
| 59 | 
            +
                # For example, in your spec:
         | 
| 60 | 
            +
                # @product = {
         | 
| 61 | 
            +
                #  'name' => 'iPhone',
         | 
| 62 | 
            +
                #  'brand' => 'Apple',
         | 
| 63 | 
            +
                #  'price' => '$299.99',
         | 
| 64 | 
            +
                #  'quantity' => '5',
         | 
| 65 | 
            +
                #  'sku' => '1234'
         | 
| 66 | 
            +
                # }
         | 
| 67 | 
            +
                # new_product_id = test_create(@product)
         | 
| 68 | 
            +
                # create_errors.should == {}
         | 
| 69 | 
            +
                # md[new_product_id].should == @product
         | 
| 70 | 
            +
                # 
         | 
| 71 | 
            +
                # This will return the result of the adapter's create method.  The master 
         | 
| 72 | 
            +
                # document (:md) should also contain the new record.
         | 
| 35 73 | 
             
                def test_create(record)
         | 
| 36 74 | 
             
                  @c.put_data(:create,{'temp-id' => record})
         | 
| 37 75 | 
             
                  @ss.create(@c.id)
         | 
| @@ -39,32 +77,72 @@ module Rhosync | |
| 39 77 | 
             
                  links ? links['l'] : nil
         | 
| 40 78 | 
             
                end
         | 
| 41 79 |  | 
| 80 | 
            +
                # Returns any errors stored in redis from the previous source adapter create
         | 
| 81 | 
            +
                # (same structure as query errors)
         | 
| 42 82 | 
             
                def create_errors
         | 
| 43 83 | 
             
                  @c.get_data(:create_errors)
         | 
| 44 84 | 
             
                end
         | 
| 45 85 |  | 
| 86 | 
            +
                # Execute the source adapter's update method.
         | 
| 87 | 
            +
                # Takes a record as hash of hashes (object_id => object)
         | 
| 88 | 
            +
                # 
         | 
| 89 | 
            +
                # For example:
         | 
| 90 | 
            +
                # test_update({'4' => {'price' => '$199.99'}})
         | 
| 91 | 
            +
                # update_errors.should == {}
         | 
| 92 | 
            +
                # test_query
         | 
| 93 | 
            +
                # md[product_id]['price'].should == '$199.99'
         | 
| 94 | 
            +
                # 
         | 
| 95 | 
            +
                # This will call the adapter's update method for object_id '4'
         | 
| 96 | 
            +
                # NOTE: To test the master document, you will need to run def test_query
         | 
| 97 | 
            +
                # as shown above
         | 
| 46 98 | 
             
                def test_update(record)
         | 
| 47 99 | 
             
                  @c.put_data(:update,record)
         | 
| 48 100 | 
             
                  @ss.update(@c.id)
         | 
| 49 101 | 
             
                end
         | 
| 50 102 |  | 
| 103 | 
            +
                # Returns any errors stored in redis from the previous source adapter update
         | 
| 104 | 
            +
                # (same structure as query errors)
         | 
| 51 105 | 
             
                def update_errors
         | 
| 52 106 | 
             
                  @c.get_data(:update_errors)
         | 
| 53 107 | 
             
                end
         | 
| 54 108 |  | 
| 109 | 
            +
                # Execute the source adapter's delete method.
         | 
| 110 | 
            +
                # Takes a record as hash of hashes (object_id => object)
         | 
| 111 | 
            +
                #
         | 
| 112 | 
            +
                # For example:
         | 
| 113 | 
            +
                # @product = {
         | 
| 114 | 
            +
                #   'name' => 'iPhone',
         | 
| 115 | 
            +
                #   'brand' => 'Apple',
         | 
| 116 | 
            +
                #   'price' => '$299.99',
         | 
| 117 | 
            +
                #   'quantity' => '5',
         | 
| 118 | 
            +
                #   'sku' => '1234'
         | 
| 119 | 
            +
                # }
         | 
| 120 | 
            +
                # test_delete('4' => @product)
         | 
| 121 | 
            +
                # delete_errors.should == {}
         | 
| 122 | 
            +
                # md.should == {}
         | 
| 123 | 
            +
                # 
         | 
| 124 | 
            +
                # This will call the adapter's delete method for product '4'
         | 
| 125 | 
            +
                # NOTE: The master document (:md) will be updated and can be
         | 
| 126 | 
            +
                # verified as shown above.
         | 
| 55 127 | 
             
                def test_delete(record)
         | 
| 56 128 | 
             
                  @c.put_data(:delete,record)
         | 
| 57 129 | 
             
                  @ss.delete(@c.id)
         | 
| 58 130 | 
             
                end
         | 
| 59 131 |  | 
| 132 | 
            +
                # Returns any errors stored in redis from the previous source adapter delete
         | 
| 133 | 
            +
                # (same structure as query errors)
         | 
| 60 134 | 
             
                def delete_errors
         | 
| 61 135 | 
             
                  @c.get_data(:delete_errors)
         | 
| 62 136 | 
             
                end
         | 
| 63 137 |  | 
| 138 | 
            +
                # Returns the master document (:md) for the source adapter stored in redis.
         | 
| 139 | 
            +
                # This is equivalent to the @result hash of hashes structure.
         | 
| 64 140 | 
             
                def md
         | 
| 65 141 | 
             
                  @s.get_data(:md)
         | 
| 66 142 | 
             
                end
         | 
| 67 143 |  | 
| 144 | 
            +
                # Returns the client document (:cd) for the source adapter + client under test.
         | 
| 145 | 
            +
                # The master document (:md) and client document (:cd) should be equal
         | 
| 68 146 | 
             
                def cd
         | 
| 69 147 | 
             
                  @c.get_data(:cd)
         | 
| 70 148 | 
             
                end
         | 
    
        data/lib/rhosync/version.rb
    CHANGED
    
    
    
        data/lib/rhosync.rb
    CHANGED
    
    | @@ -122,6 +122,10 @@ module Rhosync | |
| 122 122 | 
             
                settings_file = File.join(basedir,'settings','settings.yml') if basedir
         | 
| 123 123 | 
             
                YAML.load_file(settings_file) if settings_file and File.exist?(settings_file)
         | 
| 124 124 | 
             
              end
         | 
| 125 | 
            +
              
         | 
| 126 | 
            +
              def source_config
         | 
| 127 | 
            +
                { "sources" => Rhosync.get_config(Rhosync.base_directory)[:sources] }
         | 
| 128 | 
            +
              end
         | 
| 125 129 | 
             
              ### End Rhosync setup methods  
         | 
| 126 130 |  | 
| 127 131 |  | 
| @@ -19,7 +19,8 @@ describe "RhosyncApiGetSourceParams" do | |
| 19 19 | 
             
                  {"name"=>"sync_type", "value"=>"incremental", "type"=>"string"}, 
         | 
| 20 20 | 
             
                  {"name"=>"queue", "value"=>nil, "type"=>"string"}, 
         | 
| 21 21 | 
             
                  {"name"=>"query_queue", "value"=>nil, "type"=>"string"}, 
         | 
| 22 | 
            -
                  {"name"=>"cud_queue", "value"=>nil, "type"=>"string"} | 
| 22 | 
            +
                  {"name"=>"cud_queue", "value"=>nil, "type"=>"string"},
         | 
| 23 | 
            +
                  {"name"=>"schema", "value"=>nil, "type"=>"string"}]
         | 
| 23 24 | 
             
              end
         | 
| 24 25 |  | 
| 25 26 | 
             
            end
         | 
| @@ -5,13 +5,13 @@ describe "RhosyncApiListSources" do | |
| 5 5 |  | 
| 6 6 | 
             
              it "should list all application sources" do
         | 
| 7 7 | 
             
                post "/api/list_sources", {:api_token => @api_token}
         | 
| 8 | 
            -
                JSON.parse(last_response.body).should == ["SimpleAdapter", "SampleAdapter"]
         | 
| 8 | 
            +
                JSON.parse(last_response.body).sort.should == ["SimpleAdapter", "SampleAdapter", "FixedSchemaAdapter"].sort
         | 
| 9 9 | 
             
              end
         | 
| 10 10 |  | 
| 11 11 | 
             
              it "should list all application sources using partition_type param" do
         | 
| 12 12 | 
             
                post "/api/list_sources", 
         | 
| 13 13 | 
             
                  {:api_token => @api_token, :partition_type => 'all'}
         | 
| 14 | 
            -
                JSON.parse(last_response.body).should == ["SimpleAdapter", "SampleAdapter"]
         | 
| 14 | 
            +
                JSON.parse(last_response.body).sort.should == ["SimpleAdapter", "SampleAdapter", "FixedSchemaAdapter"].sort
         | 
| 15 15 | 
             
              end
         | 
| 16 16 |  | 
| 17 17 | 
             
              it "should list app partition sources" do
         | 
| @@ -21,7 +21,7 @@ describe "RhosyncApiListSources" do | |
| 21 21 |  | 
| 22 22 | 
             
              it "should list user partition sources" do
         | 
| 23 23 | 
             
                post "/api/list_sources", {:api_token => @api_token, :partition_type => :user}
         | 
| 24 | 
            -
                JSON.parse(last_response.body).should == ["SampleAdapter"]
         | 
| 24 | 
            +
                JSON.parse(last_response.body).sort.should == ["SampleAdapter", "FixedSchemaAdapter"].sort
         | 
| 25 25 | 
             
              end
         | 
| 26 26 |  | 
| 27 27 | 
             
            end
         | 
| @@ -4,6 +4,19 @@ | |
| 4 4 | 
             
              SimpleAdapter:
         | 
| 5 5 | 
             
                poll_interval: 600
         | 
| 6 6 | 
             
                partition_type: app
         | 
| 7 | 
            +
              FixedSchemaAdapter:
         | 
| 8 | 
            +
                poll_interval: 300
         | 
| 9 | 
            +
                schema:
         | 
| 10 | 
            +
                  version: '1.0'
         | 
| 11 | 
            +
                  property:
         | 
| 12 | 
            +
                    - name: string
         | 
| 13 | 
            +
                    - brand: string
         | 
| 14 | 
            +
                    - price: string
         | 
| 15 | 
            +
                    - image_url: blob
         | 
| 16 | 
            +
                  index: 
         | 
| 17 | 
            +
                    - by_name_brand: 'name,brand'
         | 
| 18 | 
            +
                  unique_index: 
         | 
| 19 | 
            +
                    - by_price: 'price'
         | 
| 7 20 |  | 
| 8 21 | 
             
            :development:
         | 
| 9 22 | 
             
              :licensefile: settings/license.key
         | 
    
        data/spec/doc/doc_spec.rb
    CHANGED
    
    
| @@ -16,16 +16,19 @@ describe "BulkDataJob" do | |
| 16 16 | 
             
              it "should create sqlite data file from master document" do
         | 
| 17 17 | 
             
                set_state('test_db_storage' => @data)
         | 
| 18 18 | 
             
                docname = bulk_data_docname(@a.id,@u.id)
         | 
| 19 | 
            +
                expected = { @s_fields[:name] => @data,
         | 
| 20 | 
            +
                  'FixedSchemaAdapter' => @data
         | 
| 21 | 
            +
                }
         | 
| 19 22 | 
             
                data = BulkData.create(:name => docname,
         | 
| 20 23 | 
             
                  :state => :inprogress,
         | 
| 21 24 | 
             
                  :app_id => @a.id,
         | 
| 22 25 | 
             
                  :user_id => @u.id,
         | 
| 23 | 
            -
                  :sources => [@s_fields[:name]])
         | 
| 26 | 
            +
                  :sources => [@s_fields[:name], 'FixedSchemaAdapter'])
         | 
| 24 27 | 
             
                BulkDataJob.perform("data_name" => data.name)
         | 
| 25 28 | 
             
                data = BulkData.load(docname)
         | 
| 26 29 | 
             
                data.completed?.should == true
         | 
| 27 30 | 
             
                verify_result(@s.docname(:md) => @data,@s.docname(:md_copy) => @data)
         | 
| 28 | 
            -
                validate_db(data | 
| 31 | 
            +
                validate_db(data,expected).should == true
         | 
| 29 32 | 
             
                File.exists?(data.dbfile+'.rzip').should == true
         | 
| 30 33 | 
             
                File.exists?(data.dbfile+'.hsqldb.data').should == true
         | 
| 31 34 | 
             
                File.exists?(data.dbfile+'.hsqldb.script').should == true
         | 
| @@ -33,7 +36,8 @@ describe "BulkDataJob" do | |
| 33 36 | 
             
                path = File.join(File.dirname(data.dbfile),'tmp')
         | 
| 34 37 | 
             
                FileUtils.mkdir_p path
         | 
| 35 38 | 
             
                unzip_file("#{data.dbfile}.rzip",path)
         | 
| 36 | 
            -
                 | 
| 39 | 
            +
                data.dbfile = File.join(path,File.basename(data.dbfile))
         | 
| 40 | 
            +
                validate_db(data,expected).should == true
         | 
| 37 41 | 
             
              end
         | 
| 38 42 |  | 
| 39 43 | 
             
              it "should not create hsql db files if blackberry_bulk_sync is disabled" do
         | 
| @@ -49,11 +53,11 @@ describe "BulkDataJob" do | |
| 49 53 | 
             
                data = BulkData.load(docname)
         | 
| 50 54 | 
             
                data.completed?.should == true
         | 
| 51 55 | 
             
                verify_result(@s.docname(:md) => @data,@s.docname(:md_copy) => @data)
         | 
| 52 | 
            -
                validate_db(data,@data).should == true
         | 
| 56 | 
            +
                validate_db(data,@s.name => @data).should == true
         | 
| 53 57 | 
             
                File.exists?(data.dbfile+'.hsqldb.script').should == false
         | 
| 54 58 | 
             
                File.exists?(data.dbfile+'.hsqldb.properties').should == false
         | 
| 55 59 | 
             
              end
         | 
| 56 | 
            -
             | 
| 60 | 
            +
             | 
| 57 61 | 
             
              it "should create sqlite data with source metadata" do
         | 
| 58 62 | 
             
                set_state('test_db_storage' => @data)
         | 
| 59 63 | 
             
                mock_metadata_method([SampleAdapter]) do
         | 
| @@ -69,7 +73,7 @@ describe "BulkDataJob" do | |
| 69 73 | 
             
                  verify_result(@s.docname(:md) => @data,
         | 
| 70 74 | 
             
                    @s.docname(:metadata) => {'foo'=>'bar'}.to_json,
         | 
| 71 75 | 
             
                    @s.docname(:md_copy) => @data)
         | 
| 72 | 
            -
                  validate_db(data,@data).should == true
         | 
| 76 | 
            +
                  validate_db(data,@s.name => @data).should == true
         | 
| 73 77 | 
             
                end
         | 
| 74 78 | 
             
              end
         | 
| 75 79 |  | 
    
        data/spec/server/server_spec.rb
    CHANGED
    
    | @@ -13,6 +13,8 @@ describe "Server" do | |
| 13 13 | 
             
              include Rack::Test::Methods
         | 
| 14 14 | 
             
              include Rhosync
         | 
| 15 15 |  | 
| 16 | 
            +
              it_should_behave_like "DBObjectsHelper"
         | 
| 17 | 
            +
              
         | 
| 16 18 | 
             
              before(:each) do
         | 
| 17 19 | 
             
                require File.join(get_testapp_path,@test_app_name)
         | 
| 18 20 | 
             
                Rhosync.bootstrap(get_testapp_path) do |rhosync|
         | 
| @@ -24,14 +26,12 @@ describe "Server" do | |
| 24 26 | 
             
                  :secret => "secure!"
         | 
| 25 27 | 
             
                )
         | 
| 26 28 | 
             
                Server.use Rack::Static, :urls => ["/data"], 
         | 
| 27 | 
            -
                  :root =>  File.join(File.dirname(__FILE__),'..','apps','rhotestapp')
         | 
| 29 | 
            +
                  :root =>  File.expand_path(File.join(File.dirname(__FILE__),'..','apps','rhotestapp'))
         | 
| 28 30 | 
             
              end
         | 
| 29 31 |  | 
| 30 32 | 
             
              def app
         | 
| 31 33 | 
             
                @app ||= Server.new
         | 
| 32 34 | 
             
              end
         | 
| 33 | 
            -
             | 
| 34 | 
            -
              it_should_behave_like "DBObjectsHelper"
         | 
| 35 35 |  | 
| 36 36 | 
             
              it "should show status page" do
         | 
| 37 37 | 
             
                get '/'
         | 
| @@ -116,8 +116,19 @@ describe "Server" do | |
| 116 116 | 
             
              describe "client management routes" do
         | 
| 117 117 | 
             
                before(:each) do
         | 
| 118 118 | 
             
                  do_post "/application/clientlogin", "login" => @u.login, "password" => 'testpass'
         | 
| 119 | 
            -
                  @source_config = { | 
| 120 | 
            -
                    " | 
| 119 | 
            +
                  @source_config = {
         | 
| 120 | 
            +
                    "sources"=> { 
         | 
| 121 | 
            +
                      "FixedSchemaAdapter"=>
         | 
| 122 | 
            +
                        {"schema"=>{"property"=>[{"name"=>"string"}, 
         | 
| 123 | 
            +
                          {"brand"=>"string"}, {"price"=>"string"}, 
         | 
| 124 | 
            +
                          {"image_url"=>"blob"}], "version"=>"1.0", 
         | 
| 125 | 
            +
                          "unique_index"=>[{"by_price"=>"price"}], 
         | 
| 126 | 
            +
                          "index"=>[{"by_name_brand"=>"name,brand"}]}, 
         | 
| 127 | 
            +
                          "poll_interval"=>300},
         | 
| 128 | 
            +
                       "SampleAdapter"=>{"poll_interval"=>300}, 
         | 
| 129 | 
            +
                       "SimpleAdapter"=>{"partition_type"=>"app", 
         | 
| 130 | 
            +
                         "poll_interval"=>600}}
         | 
| 131 | 
            +
                  }
         | 
| 121 132 | 
             
                end
         | 
| 122 133 |  | 
| 123 134 | 
             
                it "should respond to clientcreate" do
         | 
| @@ -190,6 +201,20 @@ describe "Server" do | |
| 190 201 | 
             
                  verify_result("test_delete_storage" => {'1'=>@product1})
         | 
| 191 202 | 
             
                end
         | 
| 192 203 |  | 
| 204 | 
            +
                it "should handle client posting broken json" do
         | 
| 205 | 
            +
                  broken_json = "{\"foo\":\"bar\"\"}"
         | 
| 206 | 
            +
                  post "/application", broken_json, {'CONTENT_TYPE'=>'application/json'}
         | 
| 207 | 
            +
                  last_response.status.should == 500
         | 
| 208 | 
            +
                  last_response.body.should == "Server error while processing client data"
         | 
| 209 | 
            +
                end
         | 
| 210 | 
            +
                
         | 
| 211 | 
            +
                it "should handle client posting broken body" do
         | 
| 212 | 
            +
                  broken_json = ['foo']
         | 
| 213 | 
            +
                  post "/application", broken_json, {'CONTENT_TYPE'=>'application/json'}
         | 
| 214 | 
            +
                  last_response.status.should == 500
         | 
| 215 | 
            +
                  last_response.body.should == "Internal server error"
         | 
| 216 | 
            +
                end
         | 
| 217 | 
            +
                
         | 
| 193 218 | 
             
                it "should get inserts json" do
         | 
| 194 219 | 
             
                  cs = ClientSync.new(@s,@c,1)
         | 
| 195 220 | 
             
                  data = {'1'=>@product1,'2'=>@product2}
         | 
| @@ -307,7 +332,7 @@ describe "Server" do | |
| 307 332 | 
             
                  data = BulkData.load(bulk_data_docname(@a.id,@u.id))
         | 
| 308 333 | 
             
                  last_response.body.should == {:result => :url, 
         | 
| 309 334 | 
             
                    :url => data.url}.to_json
         | 
| 310 | 
            -
                   | 
| 335 | 
            +
                  validate_db(data,{@s.name => @data, 'FixedSchemaAdapter' => @data})
         | 
| 311 336 | 
             
                end
         | 
| 312 337 |  | 
| 313 338 | 
             
                it "should download bulk data file" do
         | 
| @@ -318,7 +343,7 @@ describe "Server" do | |
| 318 343 | 
             
                  get JSON.parse(last_response.body)["url"]
         | 
| 319 344 | 
             
                  last_response.should be_ok
         | 
| 320 345 | 
             
                  File.open('test.data','wb') {|f| f.puts last_response.body}
         | 
| 321 | 
            -
                   | 
| 346 | 
            +
                  validate_db_file('test.data',[@s.name,'FixedSchemaAdapter'],{@s.name => @data, 'FixedSchemaAdapter' => @data})
         | 
| 322 347 | 
             
                  File.delete('test.data')
         | 
| 323 348 | 
             
                end
         | 
| 324 349 |  | 
    
        data/spec/spec_helper.rb
    CHANGED
    
    | @@ -58,6 +58,10 @@ module TestHelpers | |
| 58 58 | 
             
              def delete_data_directory
         | 
| 59 59 | 
             
                FileUtils.rm_rf(Rhosync.data_directory)
         | 
| 60 60 | 
             
              end
         | 
| 61 | 
            +
              
         | 
| 62 | 
            +
              def json_clone(data)
         | 
| 63 | 
            +
                JSON.parse(data.to_json)
         | 
| 64 | 
            +
              end
         | 
| 61 65 |  | 
| 62 66 | 
             
              def set_state(state)
         | 
| 63 67 | 
             
                state.each do |dockey,data|
         | 
| @@ -97,26 +101,52 @@ module TestHelpers | |
| 97 101 | 
             
              end
         | 
| 98 102 |  | 
| 99 103 | 
             
              def validate_db(bulk_data,data)
         | 
| 100 | 
            -
                 | 
| 104 | 
            +
                validate_db_file(bulk_data.dbfile,bulk_data.sources.members,data)  
         | 
| 105 | 
            +
              end
         | 
| 106 | 
            +
                
         | 
| 107 | 
            +
              def validate_db_file(dbfile,sources,data)  
         | 
| 108 | 
            +
                db = SQLite3::Database.new(dbfile)
         | 
| 109 | 
            +
                sources.each do |source_name|
         | 
| 110 | 
            +
                  s = Source.load(source_name,{:app_id => APP_NAME,:user_id => @u.login})
         | 
| 111 | 
            +
                  return false unless validate_db_by_name(db,s,data[s.name])
         | 
| 112 | 
            +
                end 
         | 
| 113 | 
            +
                true 
         | 
| 101 114 | 
             
              end
         | 
| 102 115 |  | 
| 103 | 
            -
              def validate_db_by_name( | 
| 104 | 
            -
                db | 
| 105 | 
            -
             | 
| 106 | 
            -
                  return false if row[0] !=  | 
| 107 | 
            -
                  return false if row[1] !=  | 
| 108 | 
            -
                  return false if row[2] !=  | 
| 109 | 
            -
                  return false if row[3] !=  | 
| 110 | 
            -
                  return false if row[4] !=  | 
| 111 | 
            -
                  return false if row[5] != get_attrib_counter(data)
         | 
| 112 | 
            -
                  return false if row[6] !=  | 
| 116 | 
            +
              def validate_db_by_name(db,s,data)
         | 
| 117 | 
            +
                db.execute("select source_id,name,sync_priority,partition,
         | 
| 118 | 
            +
                  sync_type,source_attribs,metadata from sources where name='#{s.name}'").each do |row|
         | 
| 119 | 
            +
                  return false if row[0] != s.source_id.to_s
         | 
| 120 | 
            +
                  return false if row[1] != s.name
         | 
| 121 | 
            +
                  return false if row[2] != s.priority.to_s
         | 
| 122 | 
            +
                  return false if row[3] != s.partition_type.to_s
         | 
| 123 | 
            +
                  return false if row[4] != s.sync_type.to_s
         | 
| 124 | 
            +
                  return false if row[5] != (s.schema ? "" : get_attrib_counter(data))
         | 
| 125 | 
            +
                  return false if row[6] != s.get_value(:metadata)
         | 
| 126 | 
            +
                end
         | 
| 127 | 
            +
                data = json_clone(data)
         | 
| 128 | 
            +
                if s.schema
         | 
| 129 | 
            +
                  schema = JSON.parse(s.schema)
         | 
| 130 | 
            +
                  columns = ['object']
         | 
| 131 | 
            +
                  schema['property'].each do |column|
         | 
| 132 | 
            +
                    columns << column.keys[0]
         | 
| 133 | 
            +
                  end
         | 
| 134 | 
            +
                  db.execute("select #{columns.join(',')} from #{s.name}") do |row|
         | 
| 135 | 
            +
                    obj = data[row[0]]
         | 
| 136 | 
            +
                    columns.each_index do |i|
         | 
| 137 | 
            +
                      next if i == 0
         | 
| 138 | 
            +
                      return false if row[i] != obj[columns[i]]
         | 
| 139 | 
            +
                    end
         | 
| 140 | 
            +
                    data.delete(row[0])
         | 
| 141 | 
            +
                  end
         | 
| 142 | 
            +
                else
         | 
| 143 | 
            +
                  db.execute("select * from object_values where source_id=#{s.source_id}").each do |row|
         | 
| 144 | 
            +
                    object = data[row[2]]
         | 
| 145 | 
            +
                    return false if object.nil? or object[row[1]] != row[3] or row[0] != s.source_id.to_s
         | 
| 146 | 
            +
                    object.delete(row[1])
         | 
| 147 | 
            +
                    data.delete(row[2]) if object.empty?
         | 
| 148 | 
            +
                  end
         | 
| 113 149 | 
             
                end
         | 
| 114 | 
            -
                db.execute("select * from object_values").each do |row|
         | 
| 115 | 
            -
                  object = data[row[2]]
         | 
| 116 | 
            -
                  return false if object.nil? or object[row[1]] != row[3] or row[0] != @s.source_id.to_s
         | 
| 117 | 
            -
                  object.delete(row[1])
         | 
| 118 | 
            -
                  data.delete(row[2]) if object.empty?
         | 
| 119 | 
            -
                end 
         | 
| 120 150 | 
             
                data.empty?
         | 
| 121 151 | 
             
              end
         | 
| 122 152 |  | 
| @@ -232,6 +262,10 @@ describe "DBObjectsHelper", :shared => true do | |
| 232 262 | 
             
                @c = Client.create(@c_fields,{:source_name => @s_fields[:name]})
         | 
| 233 263 | 
             
                @s = Source.load(@s_fields[:name],@s_params)
         | 
| 234 264 | 
             
                @s = Source.create(@s_fields,@s_params) if @s.nil?
         | 
| 265 | 
            +
                @s1 = Source.load('FixedSchemaAdapter',@s_params)
         | 
| 266 | 
            +
                @s1 = Source.create({:name => 'FixedSchemaAdapter'},@s_params) if @s1.nil?
         | 
| 267 | 
            +
                config = Rhosync.source_config["sources"]['FixedSchemaAdapter']
         | 
| 268 | 
            +
                @s1.update(config)
         | 
| 235 269 | 
             
                @r = @s.read_state
         | 
| 236 270 | 
             
                @a.sources << @s.id
         | 
| 237 271 | 
             
                @a.users << @u.id
         | 
    
        metadata
    CHANGED
    
    | @@ -6,8 +6,8 @@ version: !ruby/object:Gem::Version | |
| 6 6 | 
             
              - 2
         | 
| 7 7 | 
             
              - 0
         | 
| 8 8 | 
             
              - 0
         | 
| 9 | 
            -
              -  | 
| 10 | 
            -
              version: 2.0.0. | 
| 9 | 
            +
              - beta4
         | 
| 10 | 
            +
              version: 2.0.0.beta4
         | 
| 11 11 | 
             
            platform: ruby
         | 
| 12 12 | 
             
            authors: 
         | 
| 13 13 | 
             
            - Rhomobile
         | 
| @@ -15,7 +15,7 @@ autorequire: | |
| 15 15 | 
             
            bindir: bin
         | 
| 16 16 | 
             
            cert_chain: []
         | 
| 17 17 |  | 
| 18 | 
            -
            date: 2010-05- | 
| 18 | 
            +
            date: 2010-05-20 00:00:00 -07:00
         | 
| 19 19 | 
             
            default_executable: rhosync
         | 
| 20 20 | 
             
            dependencies: 
         | 
| 21 21 | 
             
            - !ruby/object:Gem::Dependency 
         | 
| @@ -412,6 +412,7 @@ files: | |
| 412 412 | 
             
            - spec/apps/rhotestapp/settings/license.key
         | 
| 413 413 | 
             
            - spec/apps/rhotestapp/settings/settings.yml
         | 
| 414 414 | 
             
            - spec/apps/rhotestapp/sources/base_adapter.rb
         | 
| 415 | 
            +
            - spec/apps/rhotestapp/sources/fixed_schema_adapter.rb
         | 
| 415 416 | 
             
            - spec/apps/rhotestapp/sources/sample_adapter.rb
         | 
| 416 417 | 
             
            - spec/apps/rhotestapp/sources/simple_adapter.rb
         | 
| 417 418 | 
             
            - spec/apps/rhotestapp/sources/sub_adapter.rb
         | 
| @@ -513,6 +514,7 @@ test_files: | |
| 513 514 | 
             
            - spec/app_spec.rb
         | 
| 514 515 | 
             
            - spec/apps/rhotestapp/application.rb
         | 
| 515 516 | 
             
            - spec/apps/rhotestapp/sources/base_adapter.rb
         | 
| 517 | 
            +
            - spec/apps/rhotestapp/sources/fixed_schema_adapter.rb
         | 
| 516 518 | 
             
            - spec/apps/rhotestapp/sources/sample_adapter.rb
         | 
| 517 519 | 
             
            - spec/apps/rhotestapp/sources/simple_adapter.rb
         | 
| 518 520 | 
             
            - spec/apps/rhotestapp/sources/sub_adapter.rb
         |